需要澄清关于 `Box`、`Vec` 和其他集合的(协)方差的 Rust Nomicon 部分

Need clarification on the Rust Nomicon section on (co)variance of `Box`, `Vec` and other collections

Rust Nomicon 有 an entire section on variance,除了关于 Box<T>Vec<T>T 的(共同)变体的这一小节外,我或多或少理解这一点。

Box and Vec are interesting cases because they're variant, but you can definitely store values in them! This is where Rust gets really clever: it's fine for them to be variant because you can only store values in them via a mutable reference! The mutable reference makes the whole type invariant, and therefore prevents you from smuggling a short-lived type into them.

让我困惑的是下面一行:

it's fine for them to be variant because you can only store values in them via a mutable reference!

我的第一个问题是我对可变引用的含义有点困惑。它是对 Box / Vec 的可变引用吗?

如果是这样,我只能通过可变引用在其中存储值这一事实如何证明它们的(协)方差?我了解什么是(协)方差以及将它用于 Box<T>Vec<T> 等的好处,但我很难看到 link 只能通过可变存储值参考文献和(协)方差的证明。

此外,当我们初始化一个 Box 时,值是否会在不涉及可变引用的情况下移入框中?这是否与我们只能通过可变引用在其中存储值的说法相矛盾?

最后,这个'mutable reference'是在什么情况下借来的?它们是否意味着当您调用修改 BoxVec 的方法时,您隐含地接受了 &mut self?那是提到的可变引用吗?


2018 年 5 月 2 日更新

由于这个问题我还没有得到满意的答复,我认为nomicon的解释确实令人困惑。因此,正如在下面的评论线程中所承诺的那样,我已经打开了 an issue in the Rust Nomicon repository。您可以在那里跟踪任何更新。

我认为该部分可以使用一些工作使其更清晰。

I'm slightly confused as to what the mutable reference is to. Is it a mutable reference to the Box / Vec?

没有。这意味着,如果您将值存储在 现有 Box 中,则必须通过对数据的可变引用来实现,例如使用 Box::borrow_mut()

本节试图传达的主要思想是,您无法修改 Box 的内容,同时存在对内容的另一个引用。这是有保证的,因为 Box 拥有它的内容。为了更改 Box 的内容,您必须通过获取新的可变引用来完成。

这意味着——即使您确实用较短寿命的值覆盖了内容——也没关系,因为没有其他人可以使用旧值。借阅检查员不允许。

这与函数参数不同,因为函数有一个代码块,它实际上可以用它的参数做一些事情。在 BoxVec 的情况下,您必须通过可变地借用它们来取出内容,然后才能对它们进行任何操作。

我想重点是,虽然您可以将 Box<&'static str> 转换为 Box<&'a str>(因为 Box<T> 是协变的),但您不能将 &mut Box<&'static str>&mut Box<&'a str> (因为 &mut T 是不变的)。

来自nomicom

Box and Vec are interesting cases because they're variant, but you can definitely store values in them! This is where Rust gets really clever: it's fine for them to be variant because you can only store values in them via a mutable reference! The mutable reference makes the whole type invariant, and therefore prevents you from smuggling a short-lived type into them.

考虑使用Vec方法添加一个值:

pub fn push(&'a mut self, value: T)

self 的类型是 &'a mut Vec<T> 我知道这是 nocom 所说的 mutable reference,所以实例化 Vec 的情况上述短语的最后一句变为:

类型 &'a mut Vec<T> 是不变的,因此可以防止您将短期类型走私到 Vec<T>.

同样的推理也适用于 Box。

换句话说:尽管 VecBox 是可变的,但 VecBox 包含的值总是比它们的容器长寿,因为您只能将值存储在他们通过可变引用。

考虑以下片段:

fn main() {
    let mut v: Vec<&String> = Vec::new();

    {
        let mut a_value = "hola".to_string();

        //v.push(a_ref);
        Vec::push(&mut v, &mut a_value);
    }

    // nomicom is saing that if &mut self Type was variant here we have had
    // a vector containing a reference pointing to freed memory

    // but this is not the case and the compiler throws an error
}

注意 Vec::push(&mut v, &mut a_value) 与 nocom 示例中的 overwrite(&mut forever_str, &mut &*string) 的相似性应该会有所帮助。

自从在 Nomicon 回购中打开问题以来,维护者引入了一个 revision to the section,我觉得它更加清晰。修订已合并。我认为修订版回答了我的问题。

下面我简要总结一下我所知道的。

现在与我的问题相关的部分如下(强调我的):

Box and Vec are interesting cases because they're covariant, but you can definitely store values in them! This is where Rust's typesystem allows it to be a bit more clever than others. To understand why it's sound for owning containers to be covariant over their contents, we must consider the two ways in which a mutation may occur: by-value or by-reference.

If mutation is by-value, then the old location that remembers extra details is moved out of, meaning it can't use the value anymore. So we simply don't need to worry about anyone remembering dangerous details. Put another way, applying subtyping when passing by-value destroys details forever. For example, this compiles and is fine:

 fn get_box<'a>(str: &'a str) -> Box<&'a str> {
     // String literals are `&'static str`s, but it's fine for us to
     // "forget" this and let the caller think the string won't live that long.
     Box::new("hello") }

If mutation is by-reference, then our container is passed as &mut Vec<T>. But &mut is invariant over its value, so &mut Vec<T> is actually invariant over T. So the fact that Vec<T> is covariant over T doesn't matter at all when mutating by-reference.

这里的关键点实际上是 &mut Vec<T> 相对于 T 的不变性和 &mut T 相对于 T 的不变性之间的平行。

在修改后的 nomicon 部分前面解释了为什么通用 &mut T 不能在 T 上协变。 &mut T借用T,但不拥有T,意思是还有其他东西引用了T,对其生命周期有一定的期望。

但是如果我们被允许在 T 上传递 &mut T 协变,那么 nomicon 示例中的 overwrite 函数展示了我们如何打破 T 的生命周期在调用者的位置 一个不同的位置(即在 overwrite 的主体内)。

在某种意义上,允许类型构造函数在 T 上的协变允许我们在传递类型构造函数时 'forget the original lifetime of T',而这个 'forgetting the original lifetime of T' 对于 &T 是可以的因为我们没有机会通过它修改 T,但是当我们有一个 &mut T 时很危险,因为我们有能力 修改 T 在忘记关于它的一生细节之后。这就是 &mut T 需要在 T 上保持不变的原因。

似乎 nomicon 试图表达的观点是:Box<T> 可以在 T 上协变,因为它不会引入不安全性。

这种协方差的后果之一是我们在按值传递 Box<T> 时允许 'forget the original lifetime of T'。但这不会引入不安全性,因为当我们按值传递时,我们保证在 Box<T> 被移动的位置没有 T 的其他用户。旧位置上没有其他人指望 T 的前一个生命周期在搬家后仍然如此。

但更重要的是,Box<T>T 上的协变性在对 Box<T> 进行可变引用时不会引入不安全性,因为 &mut Box<T> 是不变的在 Box<T> 上,因此在 T 上不变。因此,类似于上面的 &mut T 讨论,我们无法通过忘记有关 T 的生命周期细节然后在之后修改它来通过 &mut Box<T> 执行生命周期恶作剧。