Lifetime that lives until the end of the program

As I understand it, 'static is a lifetime that lives from the start until the end of the program, that's why only static variables or string literals can be 'static.

But how can I write a lifetime of some object that starts at heap-allocation and lives until the end of the program; in other words: a heap-allocated object that never gets dropped?

No, that’s not true. Lifetimes always only talk about the end-of-lifetime of a thing. The start-of-lifetime is irrelevant for soundness (i.e. preventing use-after-free and the like), and thus not tracked by the borrow checker.

So 'static already is that lifetime “that lives until the end of the program”. This is also demonstrated by the fact that you can turn e.g. a Box<i32> into &'static mut i32 by leaking it

let b: Box<i32> = Box::new(42);
let x: &'static mut i32 = Box::leak(b);

(in the playground)

Which should answer your question eluding to

4 Likes

Lifetimes always only talk about the end-of-lifetime of a thing. The start-of-lifetime is irrelevant for soundness (i.e. preventing use-after-free and the like), and thus not tracked by the borrow checker.

Oh gotcha, that makes sense. I think I was mislead by the wording in Rust by Example. Thanks!

I remember it took me a long time myself to come to this realization, so don’t feel bad about it; probably the wording in lots of learning resources is somewhat inaccurate with regards to this detail anyways.

I personally had to come up with this conclusion myself when being somewhat surprised that something like this works

let mut x: Vec<&i32> = vec![];

let a = 42;
x.push(&a);
println!("{x:?}");

let b = 1337;
x.push(&b);
println!("{x:?}");

let c = 314159265;
x.push(&c);
println!("{x:?}");

What type is x? In particular, what lifetime 'a is used in its type Vec<&'a i32>? The lifetime of those &i32 references in the Vec cannot be longer than the lifetime of the shortest reference, &c, otherwise we couldn’t push &c; yet we’re working with that Vec, pushing things into it, and printing the contents, way before c even exists. Conclusion: lifetimes only track the end-of-lifetime of a thing, and the references to a, b, and c can have “the same lifetime” in the sense of how rustc interprets “lifetimes”, even though one reference exists and is used before the other one could ever be created.

(There’s a second subtlety going on here in that the Vec gets dropped after a, b, and c stop existing; many standard-library types including Vec use the unstable #[may_dangle] attribute to allow this kind of pattern nonetheless, by promising that dropping a Vec<T> doesn’t access the contained T in any way beyond dropping it. In particular for T being &i32 this amounts to no access at all, since &i32 doesn’t do anything when dropped.)

4 Likes

There's unfortunate terminology ambiguity, because lifetimes (scopes) of loans (borrowed references) are not the same concept as object lifetimes in general.

You can have a Box type that "lives" for as long or as short as it needs, and this is not expressed in Rust using the 'a lifetime syntax. Lifetimes of references are a reference-specific thing, and don't apply to how long anything non-borrowed lives. The other lifetime concept of tracking where non-borrowed objects are created and where they are destroyed in Rust is tracked using exclusive ownership and moves, and this doesn't have any 'a syntax.

For example:


fn static_lifetime<T: 'static>(_t: T) {}

fn main() {
   let a = String::new();
 
   // this is invalid! 'lifetime applies to &a, and is too short
   // static_lifetime(&a); 

   // valid! lifetime of a is very short, but 'lifetime syntax doesn't apply
   static_lifetime(a); 
}