将 Rust 中的错误与特征对象生命周期混淆

Confusing error in Rust with trait object lifetime

谁能告诉我下面的代码有什么问题?编译器抱怨生命周期,但错误消息完全没有意义。我已经尝试了所有我能想到的方法,但似乎没有任何帮助。

use std::borrow::BorrowMut;

trait Trait<'a> {
    fn accept(&mut self, &'a u8);
}

struct Impl<'a>{
    myref: Option<&'a u8>,
}
impl<'a> Trait<'a> for Impl<'a> {
    fn accept(&mut self, inp: &'a u8) { self.myref = Some(inp); }
}

fn new<'a>() -> Box<Trait<'a> + 'a> {
    Box::new(Impl{myref: None})
}

fn user<'a>(obj: &mut Trait<'a>) {}

fn parent<'a>(x: &'a u8) {
    let mut pool = new();
    user(pool.borrow_mut());
}

编译错误为

error: `pool` does not live long enough
  --> src/wtf.rs:22:10
   |
22 |     user(pool.borrow_mut());
   |          ^^^^ does not live long enough
23 | }
   | - borrowed value dropped before borrower
   |
   = note: values in a scope are dropped in the opposite order they are created

这完全没有意义。借款人如何活得更久?我什至没有使用借来的价值!

请注意,错误消息还有更多内容:

error: `pool` does not live long enough
  --> src/main.rs:25:10
   |>
25 |>     user(pool.borrow_mut());
   |>          ^^^^
note: reference must be valid for the block at 23:25...
  --> src/main.rs:23:26
   |>
23 |> fn parent<'a>(x: &'a u8) {
   |>                          ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 24:25
  --> src/main.rs:24:26
   |>
24 |>     let mut pool = new();
   |>                          ^

我们来看看user:

fn user<'a>(obj: &mut Trait<'a>) {}

这表示它将接受对使用生命周期参数化的特征对象的可变引用(具有未命名的生命周期)'a

转向new,我会说该方法高度可疑:

fn new<'a>() -> Box<Trait<'a> + 'a> {
    Box::new(Impl { myref: None })
}

这表示它将 return 一个盒装特征对象 无论调用者指定什么生命周期 That basically never makes sense

综上所述,我不清楚为什么代码选择使用 borrow_mut。我会更直接地写:

user(&mut *pool);

这取消引用 Box<Trait> 以获得 Trait,然后采用可变引用,产生 &mut Trait,编译。

我目前无法解释为什么 BorrowMut 行为不同。

我不确定为什么会出现这个错误,但我可以给出解决方案!

首先,似乎使用 borrow_mut 不必要地限制了返回引用的生命周期。使用运算符创建引用解决了错误。

fn parent() {
    let mut pool = new();
    user(&mut *pool);
}

但是,如果我们这样做,我们可以通过在 user 中添加绑定到 Trait 对象的生命周期来解决错误s obj 参数。

fn user<'a>(obj: &mut (Trait<'a> + 'a)) {}

好的,这个确实有道理,但由于生命周期省略,很难看到。因此,这是您的代码,其中 所有 生命周期都明确写出,并剔除不相关的细节:

use std::borrow::BorrowMut;

trait Trait<'a> {}

struct Impl<'a> {
    myref: Option<&'a u8>,
}

impl<'a> Trait<'a> for Impl<'a> {}

fn new<'a>() -> Box<Trait<'a> + 'a> {
    Box::new(Impl { myref: None })
}

fn user<'a, 'b>(obj: &'b mut (Trait<'a> + 'b)) {}

fn parent() {
/* 'i: */   let mut pool/*: Box<Trait<'x> + 'x>*/ = new();
/* 'j: */   let pool_ref/*: &'i mut Box<Trait<'x> + 'x>*/ = &mut pool;
            /* BorrowMut<T>::borrow_mut<'d>(&'d mut Self) -> &'d mut T */
/* 'k: */   let pool_borrow/*: &'i mut (Trait<'x> + 'x)*/ = Box::borrow_mut(pool_ref);
            user(pool_borrow);
}

现在,从parent的最后一行来看,我们可以通过阅读user的定义并代入我们在parent中的生命周期来计算出以下等价]:

  • 'a = 'x
  • 'b = 'i
  • 'b = 'x

此外,这让我们得出结论:

  • 'x = 'i

这就是问题所在。由于您定义 user 的方式,您将自己置于 pool_ref 借用的生命周期(等于 pool 存储位置的生命周期're borrowing from) 必须与在 pool.

存储 的事物中使用的生命周期 'x 相同

这有点像Box在它存在之前能够有一个指向它自己的指针,这没有任何意义。

无论哪种方式,修复都很简单。将 user 更改为实际具有正确的类型:

fn user<'a, 'b>(obj: &'b mut (Trait<'a> + 'a)) {}

这与 new 生成的类型匹配。或者,只是不要使用 borrow_mut:

user(&mut *pool)

这个有效,因为它是"re-borrowing"。调用 borrow_mut 或多或少会直接转换生命周期,但重新借用允许编译器将借用范围缩小到更短的生命周期。换句话说,显式调用 borrow_mut 不允许编译器有足够的自由来 "fudge" 使它们全部排列的生命周期,重新借用 确实 .

顺便提一句:

I'm not even using the borrowed value!

不相关。 Rust 在本地完全 进行类型和生命周期检查。它从不查看另一个函数的主体以查看它在做什么;它单独出现在界面上。编译器既不检查也不关心你在内部一个不同的函数中做什么。