Skip to content

Misleading suggestion to add lifetime bounds #112947

@BigPeteB

Description

@BigPeteB

Code

pub struct Foo {
}
pub struct Bar<'f> {
    pub foo: &'f mut Foo,
}
pub struct Quux<'b, 'f> {
    pub bar: &'b mut Bar<'f>,
}

impl<'f> Bar<'f> {
    pub fn quux<'b>(&'b mut self) -> Quux {
        Quux { bar: self }
    }
}

#[test]
fn test_bar() {
    let mut foo = Foo {};
    let mut bar = Bar { foo: &mut foo };
    let quux = bar.quux();
    drop(quux);
    let _quux2 = bar.quux();
}

Current output

error: lifetime may not live long enough
  --> src/lib.rs:12:9
   |
10 | impl<'f> Bar<'f> {
   |      -- lifetime `'f` defined here
11 |     pub fn quux<'b>(&'b mut self) -> Quux {
   |                 -- lifetime `'b` defined here
12 |         Quux { bar: self }
   |         ^^^^^^^^^^^^^^^^^^ method was supposed to return data with lifetime `'f` but it is returning data with lifetime `'b`
   |
   = help: consider adding the following bound: `'b: 'f`
   = note: requirement occurs because of the type `Quux<'_, '_>`, which makes the generic argument `'_` invariant
   = note: the struct `Quux<'b, 'f>` is invariant over the parameter `'f`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

Desired output

help: consider annotating the return type to satisfy the necessary bounds: `Quux<'_, 'f>`

Rationale and extra context

The suggestion "consider adding the following bound: 'b: 'f" is incorrect in this case. That gets me back to the original error I was trying to solve (which is "error[E0499]: cannot borrow bar as mutable more than once at a time").

The correct suggestion is that the return type needs to be explicitly annotated:

    pub fn quux<'b>(&'b mut self) -> Quux<'b, 'f> {

(In this case, it's actually sufficient to just annotate it Quux<'_, 'f>.)

To be honest, I don't understand what the compiler means when it says "type Quux<'_, '_>, which makes the generic argument '_ invariant". Maybe if I understood that, I might have been able to see the correct solution sooner. But even if I had, the "help" suggestion that the compiler suggests is not a correct fix here.

Other cases

Adding/removing a lifetime annotation here or there can easily bring you to wildly different errors, none of which teach you the correct solution.

My original definition for Quux was

pub struct Quux<'a> {
    pub bar: &'a mut Bar<'a>,
}

After a long time, I figured out that this doesn't work because it effectively tells the compiler "as long as Bar lives, so must Quux", which leads to a contradiction. That led me to put two lifetimes on Quux, but that change by itself doesn't fix the problem.

If you do

impl<'f> Bar<'f> {
    pub fn quux(&mut self) -> Quux {
        Quux { bar: self }
    }
}

then the error you get is

error: lifetime may not live long enough
  --> src/lib.rs:12:9
   |
10 | impl<'f> Bar<'f> {
   |      -- lifetime `'f` defined here
11 |     pub fn quux(&mut self) -> Quux {
   |                 - let's call the lifetime of this reference `'1`
12 |         Quux { bar: self }
   |         ^^^^^^^^^^^^^^^^^^ method was supposed to return data with lifetime `'f` but it is returning data with lifetime `'1`
   |
   = note: requirement occurs because of the type `Quux<'_, '_>`, which makes the generic argument `'_` invariant
   = note: the struct `Quux<'b, 'f>` is invariant over the parameter `'f`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

This message doesn't provide any suggestion for how to change it. By pointing to the &mut self parameter, I thought the fix was to annotate the lifetime as &'f mut self. (Yes, that's the wrong lifetime, but when I only had a single lifetime named 'a, there was only the one choice for what lifetime to put if it wanted an explicit lifetime.)

Doing that, however, yields this messages, which is what originally got me into this mess:

error[E0499]: cannot borrow `bar` as mutable more than once at a time
  --> src/lib.rs:22:18
   |
20 |     let quux = bar.quux();
   |                ---------- first mutable borrow occurs here
21 |     drop(quux);
22 |     let _quux2 = bar.quux();
   |                  ^^^^^^^^^^
   |                  |
   |                  second mutable borrow occurs here
   |                  first borrow later used here

This was extremely confusing, because it wasn't apparent why bar must live that long. Why does doing bar.quux() a second time use the first borrow at all?

If you change it to something obviously wrong, like

    let quux = bar.quux();
    let _quux2 = bar.quux();
    drop(quux);

then you get

error[E0499]: cannot borrow `bar` as mutable more than once at a time
  --> src/lib.rs:21:18
   |
20 |     let quux = bar.quux();
   |                ---------- first mutable borrow occurs here
21 |     let _quux2 = bar.quux();
   |                  ^^^^^^^^^^ second mutable borrow occurs here
22 |     drop(quux);
   |          ---- first borrow later used here

which is definitely clearer, and fairly obvious why it's incorrect. But the former message doesn't explain at all why the second bar.quux() would need both borrows to be alive at the same time.

For a while I thought there might be something magical about self that was affecting the lifetime. I tried implementing equivalent functions outside of a struct using ordinary parameters:

fn get_quux<'b, 'f>(bar: &'b mut Bar<'f>) -> Quux {
    Quux { bar }
}

#[test]
fn test_bar_no_struct() {
    let mut foo = Foo {};
    let mut bar = Bar { foo: &mut foo };
    let quux = get_quux(&mut bar);
    drop(quux);
    let quux2 = get_quux(&mut bar);
}

This gives a completely different error, which is much more helpful!

error[E0106]: missing lifetime specifiers
    --> src/lib.rs:1129:46
     |
1129 | fn get_quux<'b, 'f>(bar: &'b mut Bar<'f>) -> Quux {
     |                          ---------------     ^^^^ expected 2 lifetime parameters
     |
     = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
note: these named lifetimes are available to use
    --> src/lib.rs:1129:13
     |
1129 | fn get_quux<'b, 'f>(bar: &'b mut Bar<'f>) -> Quux {
     |             ^^  ^^
help: consider using one of the available lifetimes here
     |
1129 | fn get_quux<'b, 'f>(bar: &'b mut Bar<'f>) -> Quux<'lifetime, 'lifetime> {
     |                                                  ++++++++++++++++++++++

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-diagnosticsArea: Messages for errors, warnings, and lintsT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions