Skip to content

Commit 19510c1

Browse files
Merge pull request #105 from rodrirejala/fix/csharp-span-inline-arrays
fix(csharp-book): add Span<T> and Inline Arrays to Arrays and Slices
2 parents 78f5dd4 + 4947349 commit 19510c1

3 files changed

Lines changed: 69 additions & 9 deletions

File tree

csharp-book/book.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ additional-js = ["mermaid.min.js", "mermaid-init.js"]
1515

1616
[preprocessor.mermaid]
1717
command = "mdbook-mermaid"
18+
optional = true
1819

1920
[output.html.playground]
2021
editable = true

csharp-book/src/ch05-data-structures-and-collections.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,62 @@ fn print_string(s: &str) {
283283
}
284284
```
285285

286+
### Modern C#: Span\<T\> and Inline Arrays
287+
288+
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.
289+
290+
```csharp
291+
// C# Span<T> - view into contiguous memory
292+
Span<int> span = stackalloc int[] { 1, 2, 3, 4, 5 };
293+
span[0] = 10;
294+
295+
ReadOnlySpan<char> text = "Hello".AsSpan();
296+
297+
// Method accepting any contiguous memory view
298+
void ProcessSpan(ReadOnlySpan<int> data)
299+
{
300+
for (int i = 0; i < data.Length; i++)
301+
Console.WriteLine(data[i]);
302+
}
303+
304+
// Inline Arrays (C# 12) - fixed-size stack buffer
305+
[InlineArray(5)]
306+
struct IntBuffer
307+
{
308+
private int _element;
309+
}
310+
```
311+
312+
```rust
313+
// Rust &[T] / &mut [T] - borrowed view into contiguous memory
314+
let mut array = [1, 2, 3, 4, 5];
315+
let slice: &mut [i32] = &mut array;
316+
slice[0] = 10;
317+
318+
let slice: &[i32] = &array;
319+
let text: &str = "Hello";
320+
321+
// Function accepting any sequential data
322+
fn process_slice(data: &[i32]) {
323+
for (i, num) in data.iter().enumerate() {
324+
println!("Index {}: {}", i, num);
325+
}
326+
}
327+
328+
// Fixed-size arrays (stack allocated)
329+
let buffer: [i32; 5] = [0; 5];
330+
```
331+
332+
| C# | Rust |
333+
|----|------|
334+
| `Span<T>` (ref struct, stack-only) | `&mut [T]` / `&[T]` (borrowed slice) |
335+
| `ReadOnlySpan<T>` | `&[T]` (immutable slice) |
336+
| `ReadOnlySpan<char>` / `string.AsSpan()` | `&str` (string slice) |
337+
| `[InlineArray(N)]` struct (C# 12) | `[T; N]` (fixed-size array) |
338+
| `stackalloc T[]` with `Span<T>` | `let arr: [T; N] = ...` (local array) |
339+
340+
> **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.
341+
286342
***
287343

288344
## Structs vs Classes

rust-patterns-book/src/ch04-phantomdata-types-that-carry-no-data.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct Slice<'a, T> {
4646
Use `PhantomData` to prevent mixing values from different "sessions" or "contexts":
4747

4848
```rust
49+
use std::cell::RefCell;
4950
use std::marker::PhantomData;
5051

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

5758
struct Arena {
58-
data: Vec<String>,
59+
data: RefCell<Vec<String>>,
5960
}
6061

6162
impl Arena {
6263
fn new() -> Self {
63-
Arena { data: Vec::new() }
64+
Arena { data: RefCell::new(Vec::new()) }
6465
}
6566

6667
/// Allocate a string and return a branded handle
67-
fn alloc<'a>(&'a mut self, value: String) -> ArenaHandle<'a> {
68-
let index = self.data.len();
69-
self.data.push(value);
68+
fn alloc(&self, value: String) -> ArenaHandle<'_> {
69+
let mut data = self.data.borrow_mut();
70+
let index = data.len();
71+
data.push(value);
7072
ArenaHandle { index, _brand: PhantomData }
7173
}
7274

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

7982
fn main() {
80-
let mut arena1 = Arena::new();
83+
let arena1 = Arena::new();
8184
let handle1 = arena1.alloc("hello".to_string());
8285

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

8790
println!("{}", arena1.get(handle1)); //

0 commit comments

Comments
 (0)