Skip to content

Commit 0d39286

Browse files
Merge pull request #132 from microsoft/copilot/fix-lifetime-branding-example
Fix lifetime branding example to reject cross-arena handles
2 parents a5915a0 + d9a8aec commit 0d39286

1 file changed

Lines changed: 21 additions & 13 deletions

File tree

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,32 @@ struct Slice<'a, T> {
4545

4646
Use `PhantomData` to prevent mixing values from different "sessions" or "contexts":
4747

48-
```rust
48+
This example intentionally fails to compile at the cross-arena lookup, even when
49+
the handle is allocated inside the inner closure:
50+
51+
```rust,compile_fail,E0521
4952
use std::cell::RefCell;
5053
use std::marker::PhantomData;
5154
5255
/// A handle branded to a specific arena instance.
53-
/// Invariant over 'arena — prevents using a handle from one arena with another.
5456
struct ArenaHandle<'arena> {
5557
index: usize,
56-
_brand: PhantomData<*mut &'arena ()>,
58+
_phantom: PhantomData<&'arena ()>,
5759
}
5860
5961
/// An arena that brands each handle with its unique lifetime.
62+
/// Invariant over 'arena — prevents shortening the brand to match another arena.
6063
struct Arena<'arena> {
6164
data: RefCell<Vec<String>>,
62-
_phantom: PhantomData<&'arena ()>,
65+
_brand: PhantomData<*mut &'arena ()>,
6366
}
6467
6568
/// Create an arena and pass it to a closure.
6669
/// Each call gets a unique, opaque lifetime that can't be forged.
6770
fn with_arena<R>(f: impl for<'arena> FnOnce(&Arena<'arena>) -> R) -> R {
6871
let arena = Arena {
6972
data: RefCell::new(Vec::new()),
70-
_phantom: PhantomData,
73+
_brand: PhantomData,
7174
};
7275
f(&arena)
7376
}
@@ -78,7 +81,7 @@ impl<'arena> Arena<'arena> {
7881
let mut data = self.data.borrow_mut();
7982
let index = data.len();
8083
data.push(value);
81-
ArenaHandle { index, _brand: PhantomData }
84+
ArenaHandle { index, _phantom: PhantomData }
8285
}
8386
8487
/// Look up by handle — only accepts handles from THIS arena
@@ -90,17 +93,23 @@ impl<'arena> Arena<'arena> {
9093
9194
fn main() {
9295
with_arena(|arena1| {
93-
let handle1 = arena1.alloc("hello".to_string());
94-
println!("{}", arena1.get(&handle1)); // ✅
96+
with_arena(|arena2| {
97+
let handle1 = arena1.alloc("hello".to_string());
98+
println!("{}", arena1.get(&handle1)); // ✅
9599
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+
// Can't use handle1 with a different arena — compile-time error
101+
arena2.get(&handle1); // ❌ borrowed data escapes outside of closure
102+
});
100103
});
101104
}
102105
```
103106

107+
The arena must be **invariant** over `'arena`: otherwise, a shared reference to an
108+
outer arena can be shortened to an inner arena's brand. Making only the handle
109+
invariant is not enough — allocating from the outer arena inside the inner closure
110+
can then produce a handle with the inner brand. `PhantomData<*mut &'arena ()>`
111+
fixes the arena's brand, while the handle can remain covariant.
112+
104113
### Unit-of-Measure Pattern
105114

106115
Prevent mixing incompatible units at compile time, with zero runtime cost:
@@ -404,4 +413,3 @@ fn main() {
404413
</details>
405414

406415
***
407-

0 commit comments

Comments
 (0)