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


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

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),
        }
    }
}

的实现 leftright 和 value 可以在特质内移动,因为它们只依赖于特征 all 由特征定义的方法,而不是实现细节。

这很好用 value但是  同 left 和 right。例如,如果我尝试移动执行 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?


5631
2018-02-28 16:43


起源

码 编译 同 非词汇生命  #![feature(nll)] - Tim Diekmann
是的,核心差异似乎是NLL允许引用,该引用引用的数据不会超过引用。 fn f<'a, 'b>() { let _: &'a &'b (); } - dtolnay
如果你使用 关联类型而不是类型参数然后它编译。除非有一个原因,单个类型应该能够实现多个实例 BTree 特质,我建议您使用相关的类型版本。这样,当您使用时编写泛型函数 BTree,您将不需要额外的类型参数 BTree的 T。 - Francis Gagné
@FrancisGagné你是对的,相关的类型可能在这里更好;我仍然不擅长在那些和类型参数之间进行选择。感谢您指出了这一点。话虽如此,我不清楚为什么关联类型没有与类型参数相同的生命周期问题...: - / - Pierre-Antoine


答案:


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

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

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

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 超过一生 'a    T 超过一生 'a (为了... &'a T 是一个有意义的类型,如上所述)。借款检查员不会让他们 呼叫 这些方法除非满足这些要求,因此实现可以假设满足这些要求。

此外,借阅检查员可以看到 如果  Self 会超越 'a  然后 也 T 会超越 'a 因为 MyBTree<T> 包含值的类型 T

这就是实施没有问题的原因 left 和 value 中 impl<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 在特征中定义的是调用者保证两者 Self 和 T 比输入寿命更长 'a。 Rust不满意的原因 left 在特征中定义的是调用者仅保证 Self 会超越 'a不是 T 会超越 'a 实现假设。

为什么编译器会抱怨方法中T的生命周期 忽视 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,
    { 
        /* ... */ 
    }
}

11
2018-02-28 17:17



非常感谢您的详细解释。事情越来越清晰。实际上,我忽略了这样一个事实:特征可以通过很多东西来实现,包括借来的类型! Rust非常可怕:) - Pierre-Antoine