Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions csharp-book/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ additional-js = ["mermaid.min.js", "mermaid-init.js"]

[preprocessor.mermaid]
command = "mdbook-mermaid"
optional = true

[output.html.playground]
editable = true
Expand Down
56 changes: 56 additions & 0 deletions csharp-book/src/ch05-data-structures-and-collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,62 @@ fn print_string(s: &str) {
}
```

### Modern C#: Span\<T\> and Inline Arrays

C# has evolved beyond traditional arrays. `Span<T>` provides type-safe, contiguous memory views that can live on the stack, while Inline Arrays (C# 12) offer fixed-size stack buffers.

```csharp
// C# Span<T> - view into contiguous memory
Span<int> span = stackalloc int[] { 1, 2, 3, 4, 5 };
span[0] = 10;

ReadOnlySpan<char> text = "Hello".AsSpan();

// Method accepting any contiguous memory view
void ProcessSpan(ReadOnlySpan<int> data)
{
for (int i = 0; i < data.Length; i++)
Console.WriteLine(data[i]);
}

// Inline Arrays (C# 12) - fixed-size stack buffer
[InlineArray(5)]
struct IntBuffer
{
private int _element;
}
```

```rust
// Rust &[T] / &mut [T] - borrowed view into contiguous memory
let mut array = [1, 2, 3, 4, 5];
let slice: &mut [i32] = &mut array;
slice[0] = 10;

let slice: &[i32] = &array;
let text: &str = "Hello";

// Function accepting any sequential data
fn process_slice(data: &[i32]) {
for (i, num) in data.iter().enumerate() {
println!("Index {}: {}", i, num);
}
}

// Fixed-size arrays (stack allocated)
let buffer: [i32; 5] = [0; 5];
```

| C# | Rust |
|----|------|
| `Span<T>` (ref struct, stack-only) | `&mut [T]` / `&[T]` (borrowed slice) |
| `ReadOnlySpan<T>` | `&[T]` (immutable slice) |
| `ReadOnlySpan<char>` / `string.AsSpan()` | `&str` (string slice) |
| `[InlineArray(N)]` struct (C# 12) | `[T; N]` (fixed-size array) |
| `stackalloc T[]` with `Span<T>` | `let arr: [T; N] = ...` (local array) |

> **Key insight:** Rust's `&[T]` combines the role of C#'s `ArraySegment<T>`, `Span<T>`, and `ReadOnlySpan<T>` — it's a fat pointer (pointer + length) that works with arrays, vectors, and subslices. C#'s Inline Arrays map naturally to Rust's `[T; N]` arrays, which are also stack-allocated by default.

***

## Structs vs Classes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct Slice<'a, T> {
Use `PhantomData` to prevent mixing values from different "sessions" or "contexts":

```rust
use std::cell::RefCell;
use std::marker::PhantomData;

/// A handle that's valid only within a specific arena's lifetime
Expand All @@ -55,33 +56,35 @@ struct ArenaHandle<'arena> {
}

struct Arena {
data: Vec<String>,
data: RefCell<Vec<String>>,
}

impl Arena {
fn new() -> Self {
Arena { data: Vec::new() }
Arena { data: RefCell::new(Vec::new()) }
}

/// Allocate a string and return a branded handle
fn alloc<'a>(&'a mut self, value: String) -> ArenaHandle<'a> {
let index = self.data.len();
self.data.push(value);
fn alloc(&self, value: String) -> ArenaHandle<'_> {
let mut data = self.data.borrow_mut();
let index = data.len();
data.push(value);
ArenaHandle { index, _brand: PhantomData }
}

/// Look up by handle — only accepts handles from THIS arena
fn get<'a>(&'a self, handle: ArenaHandle<'a>) -> &'a str {
&self.data[handle.index]
fn get<'a>(&'a self, handle: ArenaHandle<'a>) -> String {
let data = self.data.borrow();
data[handle.index].clone()
}
}

fn main() {
let mut arena1 = Arena::new();
let arena1 = Arena::new();
let handle1 = arena1.alloc("hello".to_string());

// Can't use handle1 with a different arena — lifetimes won't match
// let mut arena2 = Arena::new();
// let arena2 = Arena::new();
// arena2.get(handle1); // ❌ Lifetime mismatch

println!("{}", arena1.get(handle1)); // ✅
Expand Down