I have a struct which uses Arena
:
struct Foo<'f> {
a: Arena<u64>, // from the typed-arena crate
v: Vec<&'f u64>,
}
Is it safe to extend the lifetime of a reference into the arena so long as it is bound by the lifetime of the main struct?
impl<'f> Foo<'f> {
pub fn bar(&mut self, n: u64) -> Option<&'f u64> {
if n == 0 {
None
} else {
let n_ref = unsafe { std::mem::transmute(self.a.alloc(n)) };
Some(n_ref)
}
}
}
For more context, see this Reddit comment.
Is it safe to extend the lifetime of a reference into the arena so long as it is bound by the lifetime of the main struct?
The Arena
will be dropped along with Foo
so, in principle, this would be safe, but it would also be unnecessary because the Arena
already lives long enough.
However, this is not what your code is actually doing! The lifetime 'f
could be longer that the lifetime of the struct — it can be as long as the shortest-lived reference inside v
. For example:
fn main() {
let n = 1u64;
let v = vec![&n];
let bar;
{
let mut foo = Foo { a: Arena::new(), v };
bar = foo.bar(2);
// foo is dropped here, along with the Arena
}
// bar is still useable here because 'f is the full scope of `n`
println!("bar = {:?}", bar); // Some(8021790808186446178) - oops!
}
Trying to pretend that the lifetime is longer than it really is has created an opportunity for Undefined Behaviour in safe code.
A possible fix is to own the Arena
outside of the struct
and rely on the borrow checker to make sure that it is not dropped while it is still in use:
struct Foo<'f> {
a: &'f Arena<u64>,
v: Vec<&'f u64>,
}
impl<'f> Foo<'f> {
pub bar(&mut self, n: u64) -> Option<&'f u64> {
if n == 0 {
None
} else {
Some(self.a.alloc(n))
}
}
}
fn main() {
let arena = Arena::new();
let n = 1u64;
let v = vec![&n];
let bar;
{
let mut foo = Foo { a: &arena, v };
bar = foo.bar(2);
}
println!("bar = {:?}", bar); // Some(2)
}
Just like your unsafe version, the lifetimes express that the reference to the Arena
must be valid for at least as long as the items in the Vec
. However, this also enforces that this fact is true! Since there is no unsafe code, you can trust the borrow-checker that this will not trigger UB.