【问题标题】:Understanding error "Borrowed value does not live long enough"理解错误“借来的价值不够长”
【发布时间】:2020-03-26 21:13:51
【问题描述】:

我试图了解为什么在尝试编译以下代码时收到错误

trait Foo<'a> {
    fn foo(&'a self) -> RefHolder<'a>;
}

struct SomeType;
impl<'a> Foo<'a> for SomeType {
    fn foo(&'a self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo<'a>));
struct Container<'a> {
    pub objs: Vec<Box<dyn Foo<'a>>>,
}

fn main() {
    let mut c = Container { objs: Vec::new() };
    c.objs.push(Box::from(SomeType {}));

    let o = &c.objs[0].as_ref();
    let x = o.foo();
}

我收到错误提示

error[E0597]: `c.objs` does not live long enough
  --> src/main.rs:21:14
   |
21 |     let o = &c.objs[0].as_ref();
   |              ^^^^^^ borrowed value does not live long enough
22 |     let x = o.foo();
23 | }
   | -
   | |
   | `c.objs` dropped here while still borrowed
   | borrow might be used here, when `c` is dropped and runs the destructor for type `Container<'_>`

我很困惑为什么c.objs 仍然在main 的末尾被借用。据我了解,x 将首先被删除,然后是 o,这意味着此时不应存在对 c 的引用,从而最终可以毫无问题地删除 c

【问题讨论】:

  • 虽然我不完全理解错误消息,但它与对所有内容使用相同的生命周期 'a 有关。从除RefHolder&lt;'a&gt; 之外的任何地方删除它会使其编译,这是你想要的吗?
  • 这段代码对我来说感觉很奇怪(带有 ref 的结构可以使用相同的生命周期参数创建更多自身的 ref),但我开始认为它实际上可能揭示了一个编译器错误。特别是,如果您在 main 的每一行之后引入显式嵌套范围,当 main 的顶部范围内除了 c 之外没有其他内容时,您仍然会收到错误。
  • 这是一个更完整的示例,展示了我想要做什么。本质上,我想存储装箱的 trait 对象,并在所述 trait 上有一个方法,该方法可能会将对所述 trait 对象的引用插入到提供的列表中。虽然代码有点不同,但我得到了同样的错误“借来的值不够长”play.rust-lang.org/…
  • @captaine 我认为这是stackoverflow.com/questions/32300132/…的一个非常复杂的版本

标签: rust


【解决方案1】:

这不是一个完整的答案,因为我不完全理解错误的来源,但基本上我现在相信编译器在神秘地告诉你它没有足够的信息来解决生命周期。

我发现删除所有生命周期并仅在需要时将它们零碎地添加回来通常很有帮助,以便更好地了解正在发生的事情,所以让我们这样做。

开始:

trait Foo {
    fn foo(&self) -> RefHolder;
}

struct SomeType;

impl Foo for SomeType {
    fn foo(&self) -> RefHolder {
        RefHolder(self)
    }
}

struct RefHolder(&(dyn Foo));

// ...etc

显然,RefHolder 需要一个生命周期参数,因为它包含借用,因此将它们添加到:

trait Foo {
    fn foo(&self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo(&self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

现在foo() 需要一个。注意,不是Foo,只是函数。

trait Foo {
    fn foo<'a>(&self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo<'a>(&self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

现在编译器会更直接地告诉我们,它无法计算 impl 中 foo 的返回值的生命周期。它会说它需要和'a 一样长(因为签名)以及&amp;self 的匿名生命周期(因为它正在返回self)。但是没有人告诉它这些生命是如何相互关联的。所以我们告诉它:

trait Foo {
    fn foo<'a, 's: 'a>(&'s self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo<'a, 's: 'a>(&'s self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

现在一切都很开心。我们所做的是说's 必须至少与'a 一样长(否则self 可能在RefHolder 引用它之前被破坏)。借用检查器检查就是我们调用它时的情况,一切都很好。

之前,您开始为 trait Foo 添加生命周期,这是一场失败的战斗,因为您最终将无限回归,因为突然需要告诉 RefHolderdyn Foo 想要多长时间借来的,这不是你提前知道的。

仍然不完全确定为什么这会导致您第一次看到的确切错误,但我很高兴至少部分解决了这个问题。

【讨论】:

  • 非常感谢您的详细解释。似乎缺少的部分是从 Trait 中删除生命周期,并且只将其放在 foo 上,以及从 Container 中删除生命周期注释。虽然我很困惑两者之间的区别是什么(特征的生命周期与特征的生命周期)。更新示例:play.rust-lang.org/…
【解决方案2】:

当编译器说借来的值的寿命不够长时,实际上意味着这个值的生命周期超出了它的范围。编译器这样说是因为这是违反生命周期的最常见原因,但是由于生命周期的推导方式,您的代码恰好更复杂。

代码中的关键行是:

let x = o.foo();

有了它,代码编译得很好。其实和这个是等价的

let o : &dyn Foo = c.objs[0].as_ref();
Foo::foo(o);

(不需要额外的&amp;,但这并不重要)。

现在o 引用的生命周期是多少?好吧,因为它是从 Box::as_ref() 初始化的,并且被定义为(生命周期未删除):

fn as_ref<'s>(&'s self) -> &'s T

它与Box 本身的生命周期相同,它是从向量中获取的,使用Index 特征......所以它最终将是c.objs 的生命周期。

现在,由于你的 trait 的定义方式:

fn foo(&'a self) -> RefHolder<'a>

返回的特征具有相同的生命周期。由于代码中的每个泛型都使用相同的生命周期,Container&lt;'a&gt; 的生命周期也是如此。

也就是说,c: Container&lt;'?&gt; 的具体生命周期是其成员之一的生命周期。这类似于自引用结构,这是不允许的。

您的代码可以通过删除所有生命周期泛型来轻松编译,除了那些实际需要的泛型:

trait Foo {
    fn foo<'a>(&'a self) -> RefHolder<'a>;
}

struct SomeType;
impl Foo for SomeType {
    fn foo<'a>(&'a self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));
struct Container {
    pub objs: Vec<Box<dyn Foo>>,
}

fn main() {
    let mut c = Container { objs: Vec::new() };
    c.objs.push(Box::from(SomeType {}));

    let o : &dyn Foo = c.objs[0].as_ref();
    let x = o.foo();
}

【讨论】:

  • 非常感谢您的解释。在您的回答和@JMAA 的回答之间,我能够修复它。
猜你喜欢
  • 2020-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-29
  • 1970-01-01
  • 2016-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多