How to infer the lifetime that Lifetime annotations in method(or function) definitions?

struct V<'a> {
    s: &'a mut String,
    v: Vec<&'a String>
}

impl<'a, 'b> V<'a> {
    pub fn set(&mut self, s: &'b String){
        // self.s = s;
        self.s.push_str(s);
        self.v.push(s);
    }
}
error: lifetime may not live long enough
  --> src/main.rs:68:9
   |
64 | impl<'a, 'b> V<'a> {
   |      --  -- lifetime `'b` defined here
   |      |
   |      lifetime `'a` defined here
...
68 |         self.v.push(s);
   |         ^^^^^^^^^^^^^^ argument requires that `'b` must outlive `'a`
   |
   = help: consider adding the following bound: `'b: 'a`

But the self.s.push_str(s); is OK. Their function signatures are the same( fn xxx(&mut self, &...) ). What is the reason?
Thanks !

I read this article(2094-nll) over and over again. Seemingly understanding but not understanding.
I want to improve my Rust programming habits by learning NLL. However, I'm still very confused. Can you tell me how you overcame it?
Thanks !

But that is not where the error points to. The error points to Vec::push(), and that's completely logical: you didn't specify that 'b should outlive 'a, so the compiler can't allow a &'b String to be stored in a Vec<&'a String>.

How do the compiler checker?thanks!

I don't understand what you mean by that.

As a side note. Use &str instead of &String for function argements. Two reasons

  1. &String auto-derefs to &str, meaning that you can pass &String as well as &str to the function. More freedom for the user.
  2. &str is one level less of indirection than &String. Although I guess after the compiler is done with it that may not even make a difference.
1 Like

NLL is almost entirely concerned with borrow checking within a function body (all the inferred, unnameable borrows), whereas your example is more about variance and generic lifetime parameters.

In short, types that differ by lifetime are still distinct types. But sometimes, one of those types is a subtype of the other, and sometimes the lifetimes can be coerced to be the same so that the types match.

In your example, 'a is invariant because it's beneath a &mut. In order to push another &String into self.v, the &String needs to be a &'a String exactly.

&'b String is covariant in 'b, meaning that the 'b can become shorter. But it can't become longer. You didn't specify which is longer or shorter, so it's possible to call the method when 'b is the shorter lifetime. Since it can't be coerced to a long lifetime, you get the error message.

The suggested change (add a 'b: 'a bound) is how you specify that 'b is at least as long as 'a. When applied to the example, it means that 'b could be coerced to 'a, and thus you could call self.v.push(s).

(There's no good reason for someone to pass a longer lifetime, so you could also just use 'a instead of a distinct lifetime for s.)

1 Like

Oh, and the reason why push_str() works is that the lifetime of the string slice to be pushed is independent from the lifetime of the String being extended. The push_str() function simply copies over the characters from its argument to the string, so there needs to be no relation between the receiver String's and the argument &str's lifetime; the lifetime of the slice thus can be arbitrarily short. In contrast, Vec::push() stores its argument inside self, so as long as the vector lives, the pushed value must also be valid.

2 Likes

Also, this is both false and irrelevant — the signature of Vec::push() has an argument of type T (not &T). But whether they take &mut self doesn't matter at all; the difference lies between the type of the (non-self) argument.

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.