Confused by an error message

Hello! I've started messing with Rust and I have the following silly code:

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

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

struct Bar {}

impl<'a> Iterator for Foo<'a> {
    type Item = Baz<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        Some(Baz { bar: self.bar })
    }
}

I was expecting to get an error about me trying to have two mutable references at the same time to the same object (which is not allowed, right?) but instead I'm getting an error about lifetimes and I have absolutely no clue what the compiler is trying to tell me:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/main.rs:15:14
   |
15 |         Some(Baz { bar: self.bar })
   |              ^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 | /     fn next(&mut self) -> Option<Self::Item> {
15 | |         Some(Baz { bar: self.bar })
16 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:15:25
   |
15 |         Some(Baz { bar: self.bar })
   |                         ^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 11:6...
  --> src/main.rs:11:6
   |
11 | impl<'a> Iterator for Foo<'a> {
   |      ^^
note: ...so that the types are compatible
  --> src/main.rs:14:46
   |
14 |       fn next(&mut self) -> Option<Self::Item> {
   |  ______________________________________________^
15 | |         Some(Baz { bar: self.bar })
16 | |     }
   | |_____^
   = note: expected `std::iter::Iterator`
              found `std::iter::Iterator`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

So I have a lifetime 'a which is a name for the lifetime of Bar (the scope in which it's alive, right?) and an instance of Baz that's being moved away. It's saying that the lifetime 'a cannot outlive the anonymous lifetime defined by the scope of next. Why is that? Why would the lifetime 'a be affected by next? I mean, it's about keeping the object that's being referenced from other objects alive long enough so that the pointer doesn't become dangling, right? The lifetime 'a is a lifetime of Bar and it's still valid even after Baz is moved / goes away?

The error message also says to have a look at rustc --explain E0495 but the text doesn't even explain what's wrong with its code example and it also uses a syntax I have not seen before (what's Box::new((42,))? Is that a tuple allocated on the heap or something? Is that necessary to demonstrate the problem?).

Any help is appreciated!

One thing that's helpful when running into these issues is to explicitly list out the lifetimes and then desugar the self syntax.

This

    fn next(&mut self) -> Option<Self::Item> {
        Some(Baz { bar: self.bar })
    }

is the same as

    fn next<'b>(&'b mut self) -> Option<Self::Item> {
        Some(Baz { bar: self.bar })
    }

and if you substitute in the types, it looks like this

    fn next<'b>(self: &'b mut Foo<'a>) -> Option<Baz<'a>> {
        Some(Baz { bar: self.bar })
    }

In light of this, you get the following error message (that I condensed hopefully for clarity)

cannot infer an appropriate lifetime for lifetime parameter 'a due to conflicting requirements
note: first, the lifetime cannot outlive the lifetime 'b as defined on the method body at 14:13...

Which is basically saying that you can't give out something with a lifetime of 'a when you're only borrowing it for a lifetime of 'b. The lifetime of 'b must be shorter than 'a because you are borrowing Foo<'a> temporarily to call next. So even though you're trying to say that the underlying return value has the same lifetime as the foo struct, the Iterator trait is written such that you are only borrowing from Foo temporarily to generate the next value.

Multiple edits because I accidentally posted before finishing.

1 Like

The short version is that a &'a mut &'b mut T allows you to create a &'a mut T, but your return type said that you actually have a &'b mut T.

(you have a struct in between, but it changes nothing)

2 Likes

Are you saying that when I do bar: self.bar it doesn't copy the underlying reference to Bar, essentially disconnecting it from an instance of Foo, but rather assigns to Baz's bar a reference that is tied to a temporary borrow of Foo? I guess I'm still thinking in pointers too much.

Interesting. Where is this implemented? I'm looking at the implementation of Iterator and it seems pretty innocent.

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ...

By looking at this I would think that next would behave like any other method and that the lifetime of self within that method would be tied to the lifetime of the object it's implemented on.

I'm confused. Where do I have this? Is that the assignment bar: self.bar?


I guess I still doesn't really understand what's going on because removing those muts from both of the bar fields make the code compile without any further changes. Does that affect the lifetimes somewhat?

struct Foo<'a> {
    bar: &'a Bar,
}

struct Baz<'a> {
    bar: &'a Bar,
}

Playground

Correct.

The first thing I would point out that might help you is don't think of lifetimes as the lifetime of how long the data exists in memory, but instead think of it as how long does whatever reference you're looking at borrow the data for. In this case, Baz is borrowing the data from Foo which is borrowing it from Bar. And effectively what you're trying to set up is have Baz directly borrow the data from Bar, ignoring the fact that both Foo still exists and is still borrowing from Bar. If you think about it this way, Baz can't possibly have a lifetime that's equal to Foo because Baz first has to borrow from Foo.

I think another way of thinking about this is looking at what it looks like at the call site. Look at the example below with some fake lifetime syntax. This is a bit of a simplification of lifetimes, but hopefully it is an alternate way of explaining what I'm saying.

// Create a Bar on the stack
let bar = Bar {};
// Everything in brackets is limited to the lifetime of 'bar
'bar : { 
    // Lets create foo with an exclusive reference to bar. This borrow of bar gets dropped
    // when foo gets dropped which occurs at the end of its scope. 
    let foo : Foo<'bar>= Foo{ bar: &mut bar };
    // Now lets call next from the iterator. This is what your code is actually doing (not 
    // what you are trying to do). You are borrowing the exclusive reference to bar from
    // foo, but foo still exists, so when baz goes out of scope, foo reclaims the exclusive
    // references to bar. This is important because otherwise you wouldn't be able to call
    // foo again
    'next1: {
        let baz1: Baz<'next1> = foo.next().unwrap();
    }
    // At this point baz1 has gone out of scope so baz1 no longer has the exclusive reference
    // to bar that it borrowed from foo. This allows us to call foo.next again to repeat the 
    // borrow
    'next2: {
        let baz2: Baz<'next2> = foo.next().unwrap();
    }
}

This is how the iterator trait is expected to work. What you're trying to do would be something more equivalent to a function like this

    fn next(self: Foo<'a>) -> Option<Baz<'a>> {
        let Foo { bar } = self;
        Some(Baz { bar })
    }

Here next is taking an owned value of Foo, which means that it can do whatever it wants with Foo and the memory that Foo is pointing to can be destroyed at the end of the function. Which means now we can do whatever we want with the bar reference, because its no longer tied to Foo. In this case the lifetimes would hold up. The problem is that you would only be able to call next once, since foo is destroyed at the end of the call. This isn't a terribly useful iterator, which is why the Iterator trait does not use this signature for next.

This sort of touches on some of your other questions too, but I'll post this and look at the rest of it, before letting this response get too long.

2 Likes

&T and &mut T have different rules for when lifetimes can be shortened. In both casws the reference itself can get a shorter lifetime, but only for &T can the T get a shorter lifetime. If &mut T were to allow it, it would be possible to store a T with a shorter lifetime than the &mut T in there.

Your above comment is also helps explain your example below.

Copy is implemented for shared references (&T) but not exclusive references (&mut T). Its okay to have multiple borrows of the same data so the compiler freely allows them to be copied if they are read only which is what shared references are. But you can't have multiple exclusive references to the same data since that would lead to memory safety issues, so the compiler is very careful to make sure that when exclusively borrowing data, that there is exactly one variable holding that reference at any one time.

1 Like

One more way to show the lifetime/borrow rules is via function signatures. I made a Foo which has a shared reference to some inner data and FooMut which has an exclusive reference. And then it just goes through all possible combinations of taking Foo or FooMut by exclusive reference, shared reference or by value.

struct Foo<'a> {
    data: &'a usize,
}

struct FooMut<'a> {
    data: &'a mut usize,
}

// You can tie the output to the long lifetime if you're returning a
// shared reference
//
fn long1<'short, 'long>(foo: &'short Foo<'long>) -> &'long usize {
    foo.data
}

fn long2<'short, 'long>(foo: &'short mut Foo<'long>) -> &'long usize {
    foo.data
}

// You can't tie the output to long lifetime if the function takes a 'short
// reference to something that has an exclusive reference to the inner data.
// Effectively you must borrow from the exclusive reference held by Foo and
// that borrow must have the short lifetime
//
// Not Allowed
// fn long3<'short, 'long>(foo: &'short FooMut<'long>) -> &'long usize {
//     foo.data
// }
// Not Allowed
// fn long4<'short, 'long>(foo: &'short mut FooMut<'long>) -> &'long usize {
//     foo.data
// }

// You can also return a shared reference with long lifetime if you take the
// argument by value
fn long5<'short, 'long>(foo: Foo<'long>) -> &'long usize {
    foo.data
}

// You can return an exclusive reference with a long lifetime if you take
// an argument by value that has an exclusive reference. Here you are
// basically taking the exclusive reference out of foo (which gets destroyed)
fn long6<'short, 'long>(foo: FooMut<'long>) -> &'long mut usize {
    foo.data
}

// You can always tie the return type to the short lifetime
fn short1<'short, 'long>(foo: &'short Foo<'long>) -> &'short usize {
    foo.data
}

fn short2<'short, 'long>(foo: &'short mut Foo<'long>) -> &'short usize {
    foo.data
}

fn short3<'short, 'long>(foo: &'short FooMut<'long>) -> &'short usize {
    foo.data
}

// You must have an exclusive reference to Foo and Foo must have an exclusive
// reference to data in order to return an exclusive reference to data
fn short4<'short, 'long>(foo: &'short mut FooMut<'long>) -> &'short mut usize {
    foo.data
}

Playground

2 Likes

Thank you very much for the answers, especially @drewkett! You're awesome!

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.