Generic lifetime inferred as static

What is the explanation of this code:

    use std::sync::Arc;

    enum Enum<'a> {
        A(Arc<String>),
        B(&'a str),
    }

    pub struct Struct<'a> {
        e: Enum<'a>,
    }

    impl<'a> Struct<'a> {
        pub fn new_a() -> Self {
            Self {
                e: Enum::A(Arc::new(String::from("this"))),
            }
        }

        pub fn new_b<'b>(&self, s: &str) -> Struct<'b>
        where
            'a: 'b,
        {
            if let Enum::A(v) = &self.e {
                return Struct { e: Enum::B("sudo") };
            }
            panic!("panic");
        }
    }

    fn asd<'a>(st: &'a Struct) -> Struct<'a> {
        let s = String::from("asd");
        let zxc = st.new_b(&s);
        zxc
    }

    #[test]
    fn sdf() {
        let s = Struct::new_a();
        let z = asd(&s);
    }

Why do s and z inside function asd() inferred as 'static lifetime. In the new_b method I specify a new lifetime for returned value to be shorter than self's lifetime. Does my lifetime annotations there have meanings?, because s and z inferred as static?, This is just simplified code. Is there some indication that this might be leaking since new_b return could be shorter.

Do statics always mean static? or there are multiple lengths of statics?

Why do you think they are?

More generally, specific lifetimes aren't necessarily "chosen". The compiler just needs to prove a lifetime that meets all the constraints exists.

'a: 'b is satisfied when 'a and 'b are the same.

There's only one 'static.

Just to avoid misunderstandings, 'a: 'b is also satisfied when 'a and 'b are the same, in addition to when 'b is shorter than 'a.

2 Likes

Just stumbled upon this thread and got my answer:

So I think that:

  • 'static lifetime doesn't always mean static like we used to think(live as long as program running), but it can live as long as program running.
  • multiple 'static lifetimes can have different size/length/scope based on the source of borrow, the lender. So, static cannot always be the same even 'static lifetime inferred.
  • The inlay hints I saw in IDE suggest me the lifetime is static, but doesn't mean it actually gonna live as long as program running, because it gonna get destroyed at the end of scope, if it really lives that long, memory leak is for sure to happen.
  • As API provider, you might want to use generic lifetime so that you can give flexibility for your user to use your API, and adjust things on their end.
  • If you have to annotate things as 'static, chance is your API is harder to use, fewer places to use them since nothing is longer than static. You do this mostly to put your static variable(e.g global variable).
  • The static lifetime of literal values is longer than values owned by owned types, because literal values encoded into binary which literally live as long as program running, which is the longest static ever be. While the static of owned types are valid only as long as the scope of that owned types.

Based on my question, it's not useless to give constraint 'a: 'b, because it make sure the lifetime of reference both value of the same type, but with different lifetime, with special constraint.

I was actually expecting the inlay hints to give me specific label for lifetimes which are not "static", like 'a, 'b or anything.

Correct me If Im Wrong

That's what a 'static bound means (e.g. T: 'static), yes. It means the type is be valid "forever", which in turn means a value could be valid forever (last forever) too (e.g. if you leak it, or if it's in a static item).

But values of a type that meet a 'static bound don't have to stay alive forever. More typically, they go out of scope or are otherwise destructed.

Moreoever, the liveness scopes of values are not Rust lifetimes (those '_ things) at all. Many guides try to build intuition by saying that they are, but they are not. (Personally I find that approach breeds more confusion than clarity in the medium to long term).

There's only one 'static lifetime. Note that a 'static bound is a condition on types, and not values. The bound means that the type contains no non-'static lifetimes (including those types which contain no lifetimes at all).

My best guess as to your "size/length/scope" comment is that you're talking about value liveness (which is not a lifetime). But I'm not sure what you mean by your "borrow" statement.

I really can't speak as to whatever your IDE may be trying to tell you. Maybe something somewhere meets a 'static bound,[1] and it was trying to tell you that. Or maybe it's just plain wrong. Your OP code has no 'static bounds afterall.

The type of s could contain a 'static lifetime, but doesn't need to.

The lifetime in the reference of the type of z can't be 'static, since the value it references goes out of scope at the end of fn sdf.

The most common reasons are probably

  • multithread related, e.g. you're sending something to another thread or using multi-threaded work-stealing, and thus the thread where the value ends up may outlive the caller

  • something involving a Box<dyn Trait> that implicitly requires a 'static bound

  • something involving the Any trait

If you copy a static value into some local variable, well, the copied value isn't itself a static item (a value which lives forever with a fixed address), it's part of the local variable. Again, the type of the value may meet a 'static bound, but that doesn't mean the value lives forever.

The type of the local variable might meet a 'static bound... or it might not. Some other constraint might prevent the bound for the contianing type. In fact, the type of the copied value itself may not meet a 'static bound. For example a &'static str can coerce to a &'x str for any lifetime 'x.[2]

Such a constraint is not useless in the general case. It states a required relationship between lifetimes. (It's not very useful in this particular case either -- you could have just used 'a without promising anything extra -- but it's also not harmful. Sometimes it's useful to convey that it must be possible for a lifetime to "shrink" when that possibility isn't inherent in the types present. But it is inherent with the types in your example.)

Incidentally, types with different lifetimes are distinct types, even if they only differ by lifetime. I know what you meant, but sometimes this matters.

Lifetimes inferred in function bodies have no names, so there's not necessarily a reasonable name your IDE could give them. (Compiler errors will give them numbers sometimes ('1, '2).) I still don't know why your IDE labeled things 'static, but there's no reason why the lifetimes in the test have to be 'static.

(The compiler could have inferred non-'static lifetimes in your original test, similar to how I forced them to be non-'static in the other tests. But again, the compiler doesn't have to choose a concrete lifetime at all, it just has to prove some suitable lifetime exists. Alternatively, some would argue it always chooses the shortest possible lifetime.)


  1. or it just figured out it could meet a 'static bound ↩︎

  2. the technical term is that &'a str is covariant in 'a ↩︎