首页 文章

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

提问于
浏览
11

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

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 的生命周期 - 虽然它适用于在返回类型中提到T的方法 value

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 outlives 'a .

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

    现在让我们通过考虑如果 T 是借用类型 &() 会出现什么问题来将此应用于您的 BTree 情况 . 首先,查看您在 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 ,他们知道 Self 的寿命超过终身 'a (为了使 &'a T 成为一个有意义的类型,如上所述) . 除非满足这些要求,否则借用检查员不会让他们使用这些方法,因此实施可以假设满足这些要求 .

    此外,借阅检查器可以看到 if Self 也超过了 and ,因为 MyBTree<T> 包含 T 类型的值 .

    这就是为什么在 impl<T> BTree<T> for MyBTree<T> 中实现 leftvalue 没有问题 . 呼叫者和 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 ,但是他们 have not guaranteed 这已经超过了 'a . 例如,对于一些完全不相关的较短寿命 'b ,它们可以具有 T=&'b () . 如上所述,这将使 &'a T 等于 &'a &'b () ,这不是合法类型 .

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

    为什么编译器会在忽略T值的方法中抱怨T的生命周期 - 虽然它与返回类型中提到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,
        { 
            /* ... */ 
        }
    }
    

相关问题