Search code examples
rustlifetimeunsafe

When is it safe to extend the lifetime of references into Arenas?


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.


Solution

  • 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.