这段代码定义了一个用于表示二叉树的非常简单的特征,以及一个实现该特征的 struct :

pub trait BTree<T> {
    fn all(&self) -> Option<(&Self, &Self, &T)>;
    fn left(&self) -> Option<&Self>;
    fn right(&self) -> Option<&Self>;
    fn value(&self) -> Option<&T>;
}

pub struct MyBTree<T> {
    opt: Option<Box<(MyBTree<T>, MyBTree<T>, T)>>,
}

impl<T> BTree<T> for MyBTree<T> {
    fn all(&self) -> Option<(&Self, &Self, &T)> {
        match self.opt {
            None => None,
            Some(ref tuple) => Some((&tuple.0, &tuple.1, &tuple.2)),
        }
    }

    fn left(&self) -> Option<&Self> {
        match self.all() {
            None => None,
            Some((left, _, _)) => Some(left),
        }
    }

    fn right(&self) -> Option<&Self> {
        match self.all() {
            None => None,
            Some((right, _, _)) => Some(right),
        }
    }

    fn value(&self) -> Option<&T> {
        match self.all() {
            None => None,
            Some((_, _, value)) => Some(value),
        }
    }
}

leftrightvalue的实现可以在trait内部移动,因为它们只依赖于trait定义的all方法,而不依赖于实现细节.

这对value很有效,但notleftright有效.例如,如果我试图在trait的主体中移动left的实现,我会得到以下编译错误:

error[E0311]: the parameter type `T` may not live long enough
--> src/lib.rs:6:24
  |
6 |             match self.all() {
  |                        ^^^
  |
= help: consider adding an explicit lifetime bound for `T`
note: the parameter type `T` must be valid for the anonymous lifetime #1 defined on the method body at 5:9...
--> src/lib.rs:5:9
  |
5 | /         fn left(&self) -> Option<&Self> {
6 | |             match self.all() {
7 | |                 None => None,
8 | |                 Some((left, _, _)) => Some(left),
9 | |             }
10| |         }
  | |_________^
note: ...so that the reference type `&T` does not outlive the data it points at
--> src/lib.rs:6:24
  |
6 |             match self.all() {
  |

为什么这个问题出现在trait中,而不是在MyBTree的实现中?

为什么编译器会抱怨在返回类型中提到T的方法value中有T的生存期,而它使用的是方法value

推荐答案

为什么这个问题发生在trait中,而不是在MyBTree的实现中?

当您考虑实现具有生存期的类型的BTree<T>时,这些方法签名变得更加细微.对于涉及泛型类型参数或Self类型的所有生存期错误,我的一般建议是:关注类型为borrow 类型的情况.

borrow 类型的问题在于,引用的生命周期永远不能超过它所引用的数据.这一原则最简单的例子是:

fn f<'a, 'b>() {
    // error[E0491]: in type `&'a &'b ()`, reference has a longer
    // lifetime than the data it references
    let _: &'a &'b ();
}

Rust迫使我们保证引用所引用的数据比引用的数据更有效,在本例中,'b'a更有效.

fn f<'a, 'b: 'a>() {
    let _: &'a &'b ();
}

现在,让我们把这个应用到BTree的情况中,考虑一下如果T是borrow 的类型,比如&(),会出现什么问题.首先,看看你在impl<T> BTree<T> for MyBTree<T>中提到的以下两种方法.我写了《被省略的一生》来明确说明讨论的内容.

impl<T> BTree<T> for MyBTree<T> {
    fn left<'a>(&'a self) -> Option<&'a Self> { /* ... */ }
    fn value<'a>(&'a self) -> Option<&'a T> { /* ... */ }
}

为了让调用者调用left,他们must知道Self'a的生命周期 长.为了让调用者调用value,他们must知道Self比生命周期长'a and T比生命周期长'a(为了让&'a T成为一个有意义的类型,正如我们上面看到的).除非满足这些要求,否则借阅判断器不会允许他们使用这些方法,因此实现可以假定满足了这些要求.

此外,由于MyBTree<T>包含T类型的值,借阅判断器可以看到if Self超出'a then也超出T 'a.

这就是为什么在impl<T> BTree<T> for MyBTree<T>中实现leftvalue没有问题.调用者和MyBTree<T> struct 共同保证了所有东西都能在我们需要的时间内生存.

现在我们在BTree<T>个特征定义中使用了这些方法.

trait BTree<T> {
    fn left<'a>(&'a self) -> Option<&'a Self> { /* ... */ }
    fn value<'a>(&'a self) -> Option<&'a T> { /* ... */ }
}

这里出现了问题,因为如果调用者调用left,他们must知道Self'a长寿,但他们have not guaranteed知道T'a长寿.例如,在一些完全不相关的较短生命周期 'b中,他们可能有T=&'b ()个.正如我们在上面看到的,这将使&'a T等于&'a &'b (),这将不是一个合法的类型.

Rust对trait中定义的value感到满意的原因是调用方保证SelfT都比输入生命周期'a长.Rust对trait中定义的left不满意的原因是,调用者只保证Self'a长寿,而不是T比实现假设的'a长寿.

为什么编译器会抱怨在返回类型中没有提到T的方法value中,T的生存期是什么呢?

错误不是关于返回值,而是关于调用all().仔细看.

error[E0311]: the parameter type `T` may not live long enough
--> src/lib.rs:6:24
  |
6 |             match self.all() {
  |                        ^^^

为了调用all(),调用方负责证明输入和输出类型是有效类型.但如果T&'b (),这可能不是真的.all()将返回&'a &'b (),因此借阅判断器将阻止调用.

我们可以通过明确实现假设的保证来解决这个问题,在本例中,T'a长寿.

trait BTree<T> {
    fn left<'a>(&'a self) -> Option<&'a Self>
    where
        T: 'a,
    { 
        /* ... */ 
    }
}

Rust相关问答推荐

为什么类型需要在这个代码中手动指定,在rust?

在不重写/专门化整个函数的情况下添加单个匹配手臂到特征的方法?

如何获取Serde struct 的默认实例

如何go 除多余的(0..)在迭代中,当它不被使用时?

通过RabbitMQ取消铁 rust 中长时间运行的人造丝任务的策略

失真图像图形捕获Api

如何初始化选项<;T>;数组Rust 了?

如何轮询 Pin>?

Rust wasm 中的 Closure::new 和 Closure::wrap 有什么区别

为什么我必须使用 PhantomData?在这种情况下它在做什么?

随机函数不返回随机值

为什么是&mut发送?线程如何在安全的 Rust 中捕获 &mut?

Rust编译器通过哪些规则来确保锁被释放?

如何将 C++ 程序链接到 Rust 程序,然后将该 Rust 程序链接回 C++ 程序? (cpp -> rust -> cpp)

在多核嵌入式 Rust 中,我可以使用静态 mut 进行单向数据共享吗?

Rust 中的方法调用有什么区别?

切片不能被 `usize` 索引?

带有库+多个二进制文件的Cargo 项目,二进制文件由多个文件组成?

有没有办法使用 NASM 语法进行内联汇编?

为什么 std::iter::Peekable::peek 可变地borrow self 参数?