Good explanation of 'static

I tried to explain the 'static bound to a friend, since it's one of those concept that can seem a bit confusing. I'm not a PL expert so I thought I'd check out if you think the explanation I gave is a good one or if you have a better way of explaining it?

When you see something requiring 'static, it doesn't really mean it has to be static like static variables and &'static str which are placed in the .bss or .data segment of the executable file (and therefore exists for the liftetime of the program). It basically means that the the lifetime of the object isn't dependant on anything else and therefore is not restricted by any other part of the program.

Take String as an example. If we take a reference to a string &String, or a reference to the a slice of the string &str, these references will always depend on the lifetime of the String object they're pointing to and will not satisfy a 'static bound. If you pass in the String object itself, it will satisfy the 'static requirement even tough it might be dropped before the program ends. The special case here is &static str which points to an "object" which is guaranteed to live the entire lifetime of the program so it's fine to reference it.

'static is a requirement that affects references. It's important to remember that not all pointers are references. If you pass in a Box<String> it will be a pointer just like &String but since the String it points to is heap allocated we know by the guarantees Box gives us that the lifetime of the String object is tied to the liftime of the Box-pointer and not to a stack frame like a "normal" &String would be.

Do you know of a better explanation that you want to share (or spot any mistakes in mine)?

6 Likes

You don't touch on the difference between &'static T and T: 'static, which is very important. See also this document.

12 Likes

As far as I can tell, your explanation is accurate. I think, really, understanding the T: 'static bound is a thing that gets easier with more information about it and experience with it. So points 2) and 3) of the “Common Rust Lifetime Misconceptions” post can make some good content accompanying your own explanation. (@alice beat me to posting the same thing due to her answer being shorter ^^)

E.g. The point that &'a T is a valid type only if T: 'a makes another point: types with T: 'static are exactly the types of values that can(*) be stored in a static variable. (And note that with things like Mutex and lazy_static, static variables can be mutable, so even the values in static variables don’t need to be created at compile-time and can also be destroyed at runtime (if replaced by a new value).

There’s also the interpretation “T: 'static means that the type T does (recursively) not contain any references &'a A or &'b mut B in any fields, except—possibly(**)—for static references (&'static A or &'static mut B)”.

One might also know that lifetimes are very much a compile-time only, and even an early-compiletime-definitely-before-anything-gets-to-LLVM-only kind of concept. And on the other hand, two types Foo<'a> and Foo<'b> only differing in lifetime are different types. If you interpret T: 'static as “the type T, if written out explicitly, does not mention any non-'static lifetime”, this explains why using the Any trait requires T: 'static; one relevant application of T: 'static bounds.

Examples are important to form one’s intuition about these concepts, so the other important example lies in functions like thread::spawn which require a closure that fulfills F: 'static. In this context the intuition might be that F: 'static means that a value of type F can live without its stack frame—or—be moved outside of its stack frame. Some people like thinking about lifetimes in terms of the stack.

(*)can” as in “such a type does not need to be stored in a static variable, and moreover for many types most commonly isn’t stored in a static variable”

(**) by saying “possibly” here I want to emphasize that a “T: 'static” type does not have to contain any 'static references, does not need to have anything to do with references at all, and in many—if not most—cases does have nothing to do with references.

6 Likes

Thanks @alice. I actually forgot that this was covered by @pretzelhammer's post (which I've actually read some time ago and found excellent). It's a good place to point people for these kinds of questions.

2 Likes

Thanks @steffahn, these are great additions, and ways to see it I haven't explicitly thought of myself. Actually, in my practical experience I usually think of it like you said here:

by saying “ possibly ” here I want to emphasize that a “ T: 'static ” type does not have to contain any 'static references, does not need to have anything to do with references at all, and in many—if not most—cases does have nothing to do with references

I usually interpret the 'static bound to mean that I have to avoid references and pass in owned objects instead. When I get compile errors mentioning 'static it's usually caused by me having a reference that I didn't think of, most notably by forgetting to add move in front of a closure.

1 Like

The way I personally think of T: 'static is

A type T is 'static if values of that type can live forever.

A value that contains a short-lived reference can't live forever, because references cannot exist when they are dangling, but a String or &'static str can live forever no problems. On the other hand, the use of the word "can" highlights that they don't have to live forever. They can be destroyed at any time.

More generally T: 'a means

A type T is 'a if values of that type can live anywhere inside 'a.

So here, values that would contain a dangling reference somewhere inside the region that 'a represents are not allowed under the bound T: 'a. Again, the value doesn't have to live until the end of 'a — you can destroy it earlier than that.

As for the lifetime in &'a T, such a reference is just a reference that is guaranteed to be 'a. Of course, if the reference can live anywhere inside 'a, then the T it points at must be valid everywhere inside 'a.

2 Likes

I prefer to explain that lifetimes apply only to borrows, so they don't apply to a self-contained type that doesn't borrow anything. So T: 'static is either a reference that is valid forever, or a type that doesn't borrow anything.

3 Likes

I always think of lifetimes in terms of two complementary effects; one that describes what code inside the guard can do and another that describes what restrictions are placed on external code.

Given a generic bound T:’a:

  • From the inside, all values of type T must be either destroyed or given away before ’a expires.
  • From the outside, values of type T must not contain any borrows that will expire before the end of ’a

Given this, T:’static gives maximum flexibility to the inside code. ’static never ends, so it can do whatever it likes with the values. But T can contain essentially no borrows.

On the other end of the spectrum, you have the situation where there is no lifetime bound specified. This is roughly equivalent to the HRTB T: for<‘a> ‘a. This gives maximum flexibility to the caller: T can contain any borrows whatsoever, but the inner code is severely restricted in what it can do with the values.

1 Like

Isn't T: for<'a> 'a equivalent to T: 'static?

Your description seems to fit to the situation where T has no bound at all. Although “severely restricted” sounds worse than the situation is, since every type parameter T will always be usable for the duration of the function call. This has the effect that T will have to be “destroyed or given away before (or right when) the function ends” from the inner point of view, to use your own words (which still allows for return values of type T, or return values that own allocations containing values of type T. And leaking values of type T is also allowed, so it's more like a “no value of type T may be accessed after the function ends” kind of restriction, which explains why the restriction is not too strong after all, since functions usually don't do anything after they've returned.)

1 Like

You’re right. I shouldn’t try to write technical content before my first coffee in the morning. :confused:

I was working from the general principle that bounds always serve a dual role: They’re both axioms for the code that they guard and requirements for external code. Translating that into concrete statements about lifetime bounds didn’t quite work right.

Yeah, this is a good way to sum it up in one sentence!

I like this one. Making a distinction between the "inside" and "outside" view of lifetime bounds is actually a pretty helpful for building a good mental model.

Many thanks to those to contributed to this post. As a Rust newbie I found on-line doc coverage of this topic quite confusing. After reading this post, I think it would be helpful to Rust newbies to summarize the above comments: Here's what I`ve learned from this post (If my summary is not 100% correct, it only reflects my lingering confusion)

A type T is 'static if values of that type can live forever i.e. the entire program lifetime.
A String or &'static str can live forever, but may be destroyed at any time.

T: 'static is either a reference that is valid forever, or a type that does not borrow anything.
T:’static gives maximum flexibility to the inside code.
’static never ends, so it can do whatever you like with its values. But T can contain essentially no borrows

A type T is 'a if values of that type can live anywhere inside 'a
The value can live until 'a ends, but also can be destroyed earlier

&'a T
is a reference guaranteed to have lifetime 'a.
Since the reference can live anywhere inside 'a, the T pointed to must be valid everywhere inside 'a

Given a generic bound T:’a:
From the inside, all values of type T must be either destroyed or given away before ’a expires.
From the outside, values of type T must not contain any borrows that will expire before the end of ’a

T: for<'a> 'a equivalent to T: 'static

To elaborate on (or explain) this point some more..

The …: 'lifetime bound in Rust, which is often read as “… outlives 'lifetime”, is a transitive relationship between either a type and a lifetime or two lifetimes. Something like T: 'a is discussed in this thread so far, but there’s also 'a: 'b where the “outlives” reading is particularly common (as in, “'a outlives 'b”, which means that the lifetime 'a is “longer” than 'b (but both lifetimes are still allowed to be the same)). Transitivity here means that 'a: 'b and 'b: 'c together imply 'a: 'c; this also works with a type, i.e. T: 'a and 'a: 'b together imply T: 'b.

The 'static lifetime is always “longer” than any other lifetime, so 'static: 'a is always fulfilled. Including the case 'static: 'static which is true, too.

Now we can prove that T: for<'a> 'a and T: 'static are equivalent:

  • The bound T: for<'a> 'a is, by definition, like an infinite list of bounds T: 'a for every possible lifetime 'a. This includes 'static, hence T: for<'a> 'a implies T: 'static.
  • On the other hand, if you assume T: 'static and look at any arbitrary lifetime 'a, you always have 'static: 'a, as mentioned above. Since you have T: 'static and 'static: 'a; by transitivity you get T: 'a. By assuming T: 'static and you get T: 'a, so T: 'static implies T: 'a. Since 'a was allowed to be any arbitrary lifetime, you get that T: 'static actually implies T: for<'a> 'a.
1 Like

This naming of "outlives" could be the source of some confusing as I see it. All Strings would outlive 'static by that naming, but that's not really what it means. A String can be destroyed before the end of 'static.

2 Likes

I can share these small notes from the answer in bluss/rustfaq (which hasn't become much of anything):

Lifetime bound on a type? What's U: 'static ?

The intuitive explanation is that U: 'static means that “U does not contain references”.

Almost. It’s “U does not contain references, except 'static ones.” More formally, we can say that we have an indefinite lease on values of type U and there is no point in the program where the value goes invalid.

Note it does not mean that values of U are in a static variable or must live forever! It means that we are allowed to keep them around forever. But we don't have to. There are no "strings attached" and no best-used-before-date, because 'static is the longest lifetime there is in a Rust program.

Yes, this is indeed not what this means. I believe reading T: 'static as “T outlives 'static” isn’t problematic as long as you don’t conflate types and values. T: 'static does not mean that “all values of type T outlive 'static”. It means that ”the type T itself outlives 'static”.

For the example of String, it’s not “all Strings outlive 'static” but “String outlives 'static”.

I guess at this point, one would need to introduce some way of saying “the type T has lifetime 'a” (and define what it means) and then “T outlives 'b” just means “T’s lifetime outlives 'b”. In case of T: 'static this means that “T outlives 'static” and “T has 'static livetime” become the same, since there is no longer lifetime than 'static.

Almost. It might not contain any references, e.g. std::slice::Iter doesn't.

pub struct Iter<'a, T: 'a> {
    ptr: NonNull<T>,
    end: *const T,
    _marker: PhantomData<&'a T>,
}

Hmm, now we could discuss what “contains” actually means. I believe that from the point of view of the borrow-checker, a PhantomData<T> does contain a value of type T.

2 Likes

PhantomData is used to say that it is "as if" it contains a reference.

1 Like