Two questions about the "Reference lifetime" chapter of the offcial tutorial

First question:

In Static - Rust By Example. the code snippet contains the following part:

        // Coerce `NUM` to lifetime of `lifetime_num`:
        let coerced_static = coerce_static(&lifetime_num);

It says "Coerce NUM to lifetime of lifetime_num", but at the end we can still print out NUM at line 30.

So, before the coercion, NUM has static lifetime, after the coercion, it still has static lifetime.

What has changed? If nothing has changed, why do we need this coercion operation?

Second question:
Scroll down a little bit, the next block of code is

fn random_vec() -> &'static [usize; 100] {
    let mut rng = rand::thread_rng();
    let mut boxed = Box::new([0; 100]);
    boxed.try_fill(&mut rng).unwrap();
    Box::leak(boxed)
}

fn main() {
    let first: &'static [usize; 100] = random_vec();
    let second: &'static [usize; 100] = random_vec();
    assert_ne!(first, second)
}
  1. I wonder what does the assert_ne!(first, second) line try to prove? It compares 2 pointers, which point to 2 different pieces of data, of course they won't be equal. What does it has to do with the topic of this chapter of the tutorial, which is Static lifetime?

  2. The text above the code is

In that case it definitely doesn't live for the entire duration, but only for the leaking point onward.

Does it means that first and second (What is the "it" in the sentence BTW? Shouldn't it be "they"?) don't live before they were created?

Let's modify the code to

fn main() {
    let some_number = 1;
    let first: &'static [usize; 100] = random_vec();
}

Does the tutorial mean that first doesn't live when the line et some_number = 1; is being run?

If yes, this explanation seems to be unnecessary, because of course one thing doesn't live before it is created...

I think it may help to be more precise with terminology here.

Rust values do not have (what Rust calls) lifetimes -- as in, those '_ things. They do have liveness scopes, but where values drop is (mostly) a dynamic property, where as lifetimes are a compile time construct.

Some (but not all) types are what have lifetimes.

References to values cannot have a type with a lifetime longer than the liveness scope of the value. That's the main connection between liveness scopes and lifetimes.

Values also don't directly participate in lifetime bounds (e.g. T: 'static). Lifetime bounds check the properties of types, not values.

Most types satisfy a static bound. That doesn't mean values with that type never drop.


A lot of learning material and even official documentation is fast and loose with their terminology, and will use the term "lifetime" to mean the liveness of values without distinguishing that from Rust lifetimes ('_). Rust by example is apparently one of those.

(It was arguably a misstep to use the common term "lifetime" for those '_ things.)


The closest thing to values having a (Rust) lifetime is probably static values. These values last for the entire run of your program, and are shared and accessible by all threads. The type has to meet a static bound, and you can create a &'static _ to them.

They still don't have actually have a '_ lifetime though.

statics never drop. They're stored in your executable, not on the stack (or in a register), in contrast with local variables.

You can never have a &'static _ to a local variable, because of the variables scope - it necessarily drops or gets moved by the time the function ends.


Ok, to your questions finally.

The example would be more accurate like so.

// Returns a reference to `NUM` where its `'static`
// lifetime is coerced to that of the input argument.
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    let static_num_ref = &'static Num;
    &static_num_ref
}

It's the reference type that has a lifetime, not NUM. NUM wasn't coerced, the reference was. (Without the annotation, like in the original, it didn't necessarily have a 'static lifetime or need coerced at all.)

Nothing happened to the NUM value.

The NUM value doesn't have a lifetime. Taking references to it doesn't change the fact that it lasts for the entire run of your program.

Equality in Rust generally follows references. This isn't a pointer comparison, it's comparing the randomly generated contents. There's a (very small) chance they'll be equal.

Not sure why they have the assertion.

I think they're trying to show that Rust lifetimes only matter from the time they're created forward. This is true of all lifetimes, not just 'static. So a &'static _ can be created which points to a value that was dynamically created (provided it leaks), even though said value did not exist when the program started.

3 Likes

Thanks for your explanation. I think I can understand most of it.
But I still would like to confirm the use of some terms in Rust.
For example, a declaration like

const THRESHOLD: i32 = 10;

I wonder how do we call each of the parts in Rust?

I came from Java, so I would like to use its Java equivalent for comparison.
In Java, the example is like:

final int THRESHOLD = 10;

where:
final is the final keyword,
int is the type,
THRESHOLD is the variable name,
10 is the value of the variable.

If Rust follows the same pattern, then in const THRESHOLD: i32 = 10;,
const is the const keyword,
THRESHOLD is the variable name,
i32 is the type,
10 is the value of the variable

Is this correct? Are they aligned with how you used them in your answer above?
Thanks!

It's a "constant item", not a "variable", but other than that you have named things right.

Thanks!
I still have one question.
The answer from @quinedot says:

I wonder why a type can have a lifetime?

My understanding by now is:

  1. A value has a lifescope
  2. A reference has a lifetime

But what is the situation where a type also has a lifetime like a reference does?

For example, struct S is a type, if it has a lifetime and the lifetime ends, does that mean I can no longer create a variable of type S in a way like let variable: S = S::new()?

Or, does this refer to the T: 'a part in struct Ref<'a, T: 'a>(&'a T);?

If yes, I think it is not that the type T has a lifetime 'a, it is that any references of type T must have lifetime longer than 'a.
In other words, the generic type T in this particular function is bounded (or specified) by lifetime 'a, but the generic type T itself doesn't have a lifetime after which the type becomes unusable.

I wonder which case is correct?
Thanks!

Well, reference, of course, have to belong to some type, isn't it?

Indeed. The simples way to imagine is to use something like newtype for reference:

struct S<'a>(&'a i32);

Because you have reference in that struct said struct type, too, have limited lifetime.

It does, it does. You are mixing types and type constructors. S in examples above is type constructor and it doesn't have lifetime. But S<'a> is a concrete type and it does have lifetime.

But S<'a> is a concrete type and it does have lifetime.

Could you let me know what happens after the lifetime of the concrete type S<'a> ends?

When 'a ends, there must be no values of type S<'a> in existence at that instant or any future time.

This isn't caused to happen by the lifetime, but rather, it is a borrow-checking error if this rule is not obeyed.

I see.
I think the word "have" was used in this thread in 2 different ways.

  1. When we say "a reference has a lifetime", the "has" here means that the lifetime applies to the reference itself. The reference has a lifespan and will "die" in the future.
  2. When we say "a type has a lifetime", the "has" here means that the lifetime applies to something inside the type (like a variable or the returned value), but not the type itself. In other word, it is not that the type itself has a lifetime in the sense that it will "die" at some point, it is instead that it contains a lifetime specifier to be used inside its struct.

They are two different meanings, and using them interchangeably is confusing for new learners like me.

Thanks for the clarification~

When we say "a reference has a lifetime", the "has" here means that the lifetime applies to the reference itself. The reference has a lifespan and will "die" in the future.

No, the meaning is the same as for other types containing references. A reference has a lifetime, which means you must not use the reference after the lifetime ends (or you will get a borrow-checking error), and that's all.

The reference “will die” only in the sense that it “will become prohibited-to-continue-to-exist”, and that is the same thing that happens to types which aren't references, but contain references, and therefore have lifetime parameters too.

1 Like

You are coming from Java background, right? Maybe a little example from it would help.

Consider the following variable:

    ArrayList<String> cars = new ArrayList<String>();

Here we are declaring ArrayList of Strings. But of course you can put intergers in that ArrayList, too, no problems there:

        Integer i = Integer.valueOf(42);
        Integer j = Integer.valueOf(43);
        ((ArrayList)cars).add(i);
        ((ArrayList)cars).add(j);
        System.out.printf("%d",
            (Integer)((ArrayList)cars).get(0) +
            (Integer)((ArrayList)cars).get(1));

Everything works and there are absolutely no errors, right?

Yet most Java developers consider type of cars variable to be ArrayList<String>, not just pure ArrayList.

Lifetimes in Rust are very similar: they exist in your program and in types that your program uses. But like in Java, they are erased when you program is compiled.

By a type with a lifetime, I meant that after all generics have been resolved and you have a concrete type, there's a lifetime present. Sometimes "has a lifetime" can mean "has a non-'static lifetime" colloquially (since many lifetime concerns are about being long enough).

References are an example of such types, they just have special syntax:

&str     // Invisible parameter but `&` is a clue
&'_ str  // The same thing, more visibly
&'a str  // With a named lifetime

Ref is an example of a less special nominal struct with a lifetime parameter:

Ref<i32>     // You are allowed to completely elide it, but many feel this
             // is a misstep in the language since there's no clue like `&`
Ref<'_, i32> // The same thing, more visibly
Ref<'a, i32> // With a named lifetime

And there are also types with a lifetime parameter but no type parameters, and types with multiple lifetime parameters, etc.

Rust references are certainly special in many ways, but saying "a reference has a lifetime" isn't really different than saying "a BorrowedFd<'_> has a lifetime".

Does Vec<T> have a lifetime? It does if T resolves to some type that has a lifetime. Vec<&'a str> has a lifetime for example.

String is an example of a type with no lifetime parameters or type parameters. It can't have a lifetime.

It is sometimes useful to call things with generic parameters (e.g. lifetime or type parameters) type constructors; you don't have an actual type until you've provided "concrete" values for the parameters.

Vec isn't a type,[1] but Vec<i32> is a type.
&str isn't a type,[2] but &'static str or &'_some_inferred_lifetime str are types.

You can't have a Ref<'a, X> value outside of the region where 'a is valid.[3] But you can have a Ref<'some_other_lifetime, X>.[4] It's hard to name a lifetime that has ended, so it's hard to even demonstrate this; local lifetimes are inferred and not nameable.

The T: 'a is a bound on the type (parameter) T. A type meeting a bound is different from a type having a lifetime parameter.

Types without parameters always satisfy a 'static bound. So String: 'static holds.

Vec<T>: 'x holds when T: 'x holds.

MoreParams<'a, T>: 'b holds when

  • 'a: 'b holds, and
  • T: 'b holds

And similarly for other parameterized types.

Putting T: 'a on the type declaration like Ref<'a, T: 'a> makes the compiler enforce that T: 'a for Ref<'a, T> to be valid. Sometimes the compiler will infer such requirements itself, even if they aren't explicitly stated.[5]


For the type &'a T to be valid, the type T must satisfy the bound T: 'a. It's like &'a T had some type declaration

// Made up syntax
struct &<'a, T: 'a>;

If T doesn't have any parameters, it satisfies T: 'static, and satisfies T: 'x for any lifetime 'x. So &'a T is a valid type for any 'a in that case. For example, &'a String is always a valid type.

But if T has a lifetime,[6] let's say if T = &'b str, then the required bound is &'b str: 'a. That in turn implies 'b: 'a. So in this case, any lifetimes of T have to outlive 'a for &'a T to be a valid type.

So &'static &'b str is only a valid type when 'b: 'static, i.e. when 'b = 'static.


  1. it's a type constructor ↩︎

  2. it's a type constructor ↩︎

  3. Or where X is valid! ↩︎

  4. Provided X is valid... ↩︎

  5. But there's still reasons to make the requirement explicit which I won't go into now. ↩︎

  6. if the generic parameter T resolves to a concrete type that involves some lifetime ↩︎

Thanks for the detailed explanation.
Although I still cannot understand it, hopefully it will become simpler to me overtime.

If I see a counterfactual statement, I have a tendency to correct it, which can lead to spewing a lot of detail.

But on a practical level, you don't need to understand all the details at the beginning. You can definitely become proficient without knowing a ton of details (though depending on what route you take, some journeys require different details than others).

So if you have the basic gist of things, I'd say just continue on learning until you hit some error or other limitation you don't understand. Then revisit things and ask questions to refine your understanding.

2 Likes