Rust lifetime error

喜你入骨 提交于 2019-12-06 16:31:08

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.

Antimony

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.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!