2
votes

Can anyone tell what the lifetime error is in the following code? (simplified from my actual code) I've looked it over myself, but I can't figure out what is wrong or how to fix it. The problem comes when I try to add the Cell, but I'm not sure why.

use std::cell::Cell;

struct Bar<'a> {
    bar: &'a str,
}
impl<'a> Bar<'a> {
    fn new(foo: &'a Foo<'a>) -> Bar<'a> { Bar{bar: foo.raw} }
}

pub struct Foo<'a> {
    raw: &'a str,
    cell: Cell<&'a str>,
}
impl<'a> Foo<'a> {
    fn get_bar(&self) -> Bar { Bar::new(&self) }
}

The compiler error is

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/foo.rs:15:32
   |
15 |     fn get_bar(&self) -> Bar { Bar::new(&self) }
   |                                ^^^^^^^^
2

2 Answers

6
votes

First, the solution:

use std::cell::Cell;

struct Bar<'a> {
    bar: &'a str,
}
impl<'a> Bar<'a> {
    fn new(foo: &Foo<'a>) -> Bar<'a> { Bar{bar: foo.raw} }
}

pub struct Foo<'a> {
    raw: &'a str,
    cell: Cell<&'a str>,
}
impl<'a> Foo<'a> {
    fn get_bar(&self) -> Bar<'a> { Bar::new(&self) }
}

There are two problems in your code. The first is with get_bar, where you didn't specify the lifetime for the return type. When you don't specify lifetimes in signatures, Rust doesn't infer the correct lifetimes, it just blindly fills them in based on simple rules. In this specific case, what you get is effectively fn get_bar<'b>(&'b self) -> Bar<'b> which is obviously wrong, given the lifetime of self.raw (which was what you actually wanted) is 'a. See the Rust Book chapter on Lifetime Elision.

The second problem is that you're over-constraining the argument to Bar::new. &'a Foo<'a> means you require a borrow to a Foo for as long as the strings it's borrowing exist. But borrows within a type have to outlive values of said type, so the only lifetime valid in this case is where 'a matches the entire lifetime of the thing being borrowed... and that conflicts with the signature of get_bar (where you're saying &self won't necessarily live as long as 'a since it has its own lifetime). Long story short: remove the unnecessary 'a from the Foo borrow, leaving just &Foo<'a>.

To rephrase the above: the problem with get_bar was that you hadn't written enough constraints, the problem with Bar::new was that you'd written too many.

4
votes

DK explained which constraints were missing and why, but I figured I should explain why the code was working before I added Cell. It turns out to be due to variance inference.

If you add in the inferred lifetimes to the original code and rename the lifetime variables to be unique, you get

struct Bar<'b> {
    bar: &'b str,
}
impl<'b> Bar<'b> {
    fn new(foo: &'b Foo<'b>) -> Bar<'b> { Bar{bar: foo.raw} }
}

pub struct Foo<'a> {
    raw: &'a str,
    cell: Cell<&'a str>,
}
impl<'a> Foo<'a> {
    fn get_bar<'c>(&'c self) -> Bar<'c> { Bar::new(&self) }
}

The problem comes when calling Bar::new, because you are passing &'c Foo<'a>, to something expecting &'b Foo<'b>. Normally, immutable types in Rust are covariant, meaning that &Foo<'a> is implicitly convertable to &Foo<'b> whenever 'b is a shorter lifetime than 'a. Without the Cell, &'c Foo<'a> converts to &'c Foo<'c>, and is passed to Bar::new wiht 'b = 'c, so there is no problem.

However, Cell adds interior mutability to Foo, which means that it is no longer safe to be covariant. This is because Bar could potentially try to assign a shorter lived 'b reference back into the original Foo, but Foo requires that all of the references it holds are valid for the longer lifetime 'a. Therefore, interior mutability makes &Foo invariant, meaning you can no longer shorten the lifetime parameter implicitly.