为什么函数体在结构中编译,而不是在特征中编译?

问题描述 投票:11回答:1

此代码定义了一个非常简单的特征,用于表示二叉树和实现该特征的结构:

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的实现可以在特征内部移动,因为它们仅依赖于特征定义的all方法,而不依赖于实现细节。

这适用于value,但不适用于leftright。例如,如果我尝试在特征体中移动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() {
  |

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

为什么编译器会在忽略T值的方法中抱怨T的生命周期 - 而它与value方法一起使用,它在返回类型中提到了T?

rust traits
1个回答
11
投票

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

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

借用类型的问题是,您永远不会拥有比其引用的数据更长的生命周期的引用。这个原则最简单的例子是:

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是像&()这样的借来的类型出了什么问题。首先,看看你在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,他们必须知道Self超过终身'a。并且为了让调用者调用value他们必须知道Self超过终身'aT超过终身'a(为了使&'a T成为一个有意义的类型,如上所述)。借用检查器不会让他们调用这些方法,除非满足这些要求,因此实现可以假设满足这些要求。

此外,借入检查者可以看到,如果Self'a更长,那么T也比'a更长,因为MyBTree<T>包含T类型的值。

这就是为什么在left中实施valueimpl<T> BTree<T> for MyBTree<T>没有问题。呼叫者和MyBTree<T>结构共同保证一切都在我们需要的时候生存。

现在我们在BTree<T>特征定义中有这些方法。

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

这里的事情出了问题,因为如果来电者调用left他们必须知道Self'a更长,但他们不能保证T'a更长。例如,他们可能有T=&'b ()一些完全不相关的短寿命'b。如上所述,这将使&'a T等于&'a &'b (),这不是一种合法类型。

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

为什么编译器会在忽略T值的方法中抱怨T的生命周期 - 虽然它适用于在返回类型中提到T的方法value

那么错误不是关于返回值,而是关于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,
    { 
        /* ... */ 
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.