Skip to content

Commit 44916ce

Browse files
Merge pull request #112 from rodrirejala/fix/lifetime-branding-107
fix(rust-patterns-book): make Lifetime Branding example enforce compile-time safety
2 parents 8c7bbcb + 1fa8356 commit 44916ce

1 file changed

Lines changed: 27 additions & 17 deletions

File tree

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

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,45 +49,55 @@ Use `PhantomData` to prevent mixing values from different "sessions" or "context
4949
use std::cell::RefCell;
5050
use std::marker::PhantomData;
5151

52-
/// A handle that's valid only within a specific arena's lifetime
52+
/// A handle branded to a specific arena instance.
53+
/// Invariant over 'arena — prevents using a handle from one arena with another.
5354
struct ArenaHandle<'arena> {
5455
index: usize,
55-
_brand: PhantomData<&'arena ()>,
56+
_brand: PhantomData<*mut &'arena ()>,
5657
}
5758

58-
struct Arena {
59+
/// An arena that brands each handle with its unique lifetime.
60+
struct Arena<'arena> {
5961
data: RefCell<Vec<String>>,
62+
_phantom: PhantomData<&'arena ()>,
6063
}
6164

62-
impl Arena {
63-
fn new() -> Self {
64-
Arena { data: RefCell::new(Vec::new()) }
65-
}
65+
/// Create an arena and pass it to a closure.
66+
/// Each call gets a unique, opaque lifetime that can't be forged.
67+
fn with_arena<R>(f: impl for<'arena> FnOnce(&Arena<'arena>) -> R) -> R {
68+
let arena = Arena {
69+
data: RefCell::new(Vec::new()),
70+
_phantom: PhantomData,
71+
};
72+
f(&arena)
73+
}
6674

75+
impl<'arena> Arena<'arena> {
6776
/// Allocate a string and return a branded handle
68-
fn alloc(&self, value: String) -> ArenaHandle<'_> {
77+
fn alloc(&self, value: String) -> ArenaHandle<'arena> {
6978
let mut data = self.data.borrow_mut();
7079
let index = data.len();
7180
data.push(value);
7281
ArenaHandle { index, _brand: PhantomData }
7382
}
7483

7584
/// Look up by handle — only accepts handles from THIS arena
76-
fn get<'a>(&'a self, handle: ArenaHandle<'a>) -> String {
85+
fn get(&self, handle: &ArenaHandle<'arena>) -> String {
7786
let data = self.data.borrow();
7887
data[handle.index].clone()
7988
}
8089
}
8190

8291
fn main() {
83-
let arena1 = Arena::new();
84-
let handle1 = arena1.alloc("hello".to_string());
85-
86-
// Can't use handle1 with a different arena — lifetimes won't match
87-
// let arena2 = Arena::new();
88-
// arena2.get(handle1); // ❌ Lifetime mismatch
89-
90-
println!("{}", arena1.get(handle1)); //
92+
with_arena(|arena1| {
93+
let handle1 = arena1.alloc("hello".to_string());
94+
println!("{}", arena1.get(&handle1)); //
95+
96+
// Can't use handle1 with a different arena — compile-time error
97+
// with_arena(|arena2| {
98+
// arena2.get(&handle1); // ❌ borrowed data escapes outside of closure
99+
// });
100+
});
91101
}
92102
```
93103

0 commit comments

Comments
 (0)