Rust 生命周期错误

3

有人能告诉我以下代码中的生命周期错误是什么吗?(从我的实际代码简化而来)我自己看了一遍,但是我无法弄清楚哪里出了问题或如何修复它。当我尝试添加Cell时出现问题,但我不确定原因。

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) }
}

编译器错误是:
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个回答

7

首先,解决方案:

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) }
}

你的代码中存在两个问题。第一个问题在于get_bar函数,你没有为返回类型指定生命周期。当你在签名中没有指定生命周期时,Rust无法推断出正确的生命周期,它只会根据简单的规则盲目填充。在这种特定情况下,你实际上得到的是fn get_bar<'b>(&'b self) -> Bar<'b>,显然是错误的,因为self.raw的生命周期(你实际想要的)是'a。请参阅Rust Book章节Lifetime Elision
第二个问题是你过度约束了Bar::new的参数。&'a Foo<'a>意味着你需要对Foo进行借用,只要它所借用的字符串存在。但是,在类型内部进行借用必须超出该类型的值的生命周期,因此在这种情况下唯一有效的生命周期是'a与所借用的整个东西的生命周期相匹配......而这与get_bar的签名冲突(在其中你说&self不一定会像'a那样长,因为它有自己的生命周期)。长话短说:从Foo借用中删除不必要的'a,只保留&Foo<'a>
简而言之:get_bar的问题在于你没有写足够的约束条件,而Bar::new的问题在于你写了过多的约束条件。

@Antimony 我不确定,但我认为这可能与Cell具有内部可变性有关,这可能会影响包含类型的生命周期差异。将可变性引入混合物中往往会使借用检查器更加严格。我会给出更详细的答案,但这是我自己不太确定的一个方面。 - DK.
好观点。我忘记了方差推断。那可能就是答案了。 - Antimony

4
DK解释了缺少哪些限制条件以及原因,但我认为我应该解释一下在添加Cell之前代码为什么能够工作。事实证明这是由于variance inference导致的。
如果将推断出的生命周期添加到原始代码中并将生命周期变量重命名为唯一名称,则会得到:
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) }
}

调用Bar::new时出现问题,因为您正在传递&'c Foo<'a>,但调用方期望的是&'b Foo<'b>。通常,在Rust中,不可变类型是协变的,意味着每当'b比'a更短寿命时,&Foo<'a>会隐式转换为&Foo<'b>。没有Cell的情况下,&'c Foo<'a>转换为&'c Foo<'c>,并且传递给Bar::new,其中'b ='c,因此没有问题。
然而,Cell将内部可变性添加到Foo中,这意味着它不再是协变的。这是因为Bar可能尝试将寿命更短的'b引用分配回原始的Foo,但Foo要求它保存的所有引用都对于更长的生命周期'a有效。因此,内部可变性使得&Foo不变,意味着您不能再隐式缩短生命周期参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接