Safe to use pointer derived from reference after lifetime ends?

Is it sound to use a pointer derived from a reference after that reference's lifetime ends if you know that the instance is still valid? I can't seem to find a clearly documented answer.

For example (Playground):

use std::cell::UnsafeCell;

struct Foo<T>(UnsafeCell<T>);

struct ShortLived;

impl<T> Foo<T> {
    fn get_ptr<'a>(&'a self, _: &'a ShortLived) -> *mut T {
        self.0.get()
    }
}

fn main() {
    let foo = Foo(UnsafeCell::new(123_u32));
    let ptr = {
        let short_lived = ShortLived;
        foo.get_ptr(&short_lived)
    };

    // `ptr` was created by casting from a reference whose lifetime is no longer valid.
    // Is it still safe to use here?:
    let x = unsafe { *ptr };
    println!("x: {}", x);

    std::mem::drop(foo);
}

You seem to mean “lifetime” in a sense of giving meaning to symbolic lifetime parameters like “'a”. Those don’t have semantic meaning in Rust, they are only there to allow the borrow checker to ensure sound usage of your API. The function

impl<T> Foo<T> {
    fn get_ptr<'a>(&'a self, _: &'a ShortLived) -> *mut T {
        self.0.get()
    }
}

would also work fine it it was declaring two independent lifetime

impl<T> Foo<T> {
    fn get_ptr<'a, 'b>(&'a self, _: &'b ShortLived) -> *mut T {
        self.0.get()
    }
}

with no difference in behavior. Thus any argument for a usage of this second version of get_ptr to be sound also apply to the first version.

I wouldn’t use the work “safe to use” as that sounds as if we’re talking about a safe API, not an unsafe one, but nonetheless, your concrete code should run into no issues of doing something forbidden / invoking UB; and miri agrees with this assessment.


However this is not confirming your stated alternative. You ask

Is it sound to use a pointer derived from a reference after that reference's lifetime ends if you know that the instance is still valid?

Under your interpretation of “reference’s lifetime ends”, it’s still allowed to use the pointer here, but it’s insufficient for the pointer access that merely the instance being pointed to is still valid.

The reference itself must still be valid / alive. References are not invalidated by any restrictive choice of type annotations with lifetime parameters, but there’s plenty of operations you could execute to actually invalidate a reference, even though the pointed-to thing is still existing, valid, and unmodified.

In your example at hand for example, any mutable access to the UnsafeCell would invalidate the immutable reference to the cell. (Mutable access to the contents through immutable access to the cell is still fine, as long as you aren’t introducing data races; this is what UnsafeCell is for, after all.)

So doing something like this is not safe & can cause UB:

fn main() {
    let mut foo = Foo(UnsafeCell::new(123_u32));
    let ptr = {
        let short_lived = ShortLived;
        foo.get_ptr(&short_lived)
    };

+   // mutable access invalidates the original &Foo<u32> reference
+   let _ = &mut foo;

    // `ptr` was created by casting from a reference whose lifetime is no longer valid.
    // Is it still safe to use here?:
    let x = unsafe { *ptr };
    println!("x: {}", x);

    std::mem::drop(foo);
}
3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.