需要澄清关于`Box`,`Vec`和其他集合

时间:2018-04-24 08:15:04

标签: generics rust polymorphism covariance

Rust Nomicon有an entire section on variance这个我或多或少都能理解,除了Box<T>Vec<T>T之间的(共)变体这个小部分。

  

BoxVec是有趣的案例,因为它们是变体,但您绝对可以在其中存储值!这就是Rust变得非常聪明的地方:它们很适合变种,因为你只能通过可变引用将值存储在它们中!可变引用使整个类型不变,因此可以防止您将短期类型走私到它们中。

令我困惑的是以下几行:

  

它们很适合变种,因为你只能通过可变引用来存储中的值

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

如果是这样的话,我只能通过可变引用在其中存储值的事实如何证明它们的(共)方差?我理解(共)方差是什么以及将它用于Box<T>Vec<T>等的好处,但我很难看到只能通过可变引用存储值与理由之间的联系(共)方差。

另外,当我们初始化Box时,是否在没有涉及可变引用的情况下将值移入框中?这是否与我们只能通过可变引用存储值的声明相矛盾?

最后,在什么情况下借用这个“可变参考”?他们是否意味着当您调用修改BoxVec的方法时,您隐含地使用&mut self?这是提到的可变参考吗?

2018年5月2日更新

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

4 个答案:

答案 0 :(得分:2)

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

  

我对可变引用的含义感到有些困惑。它是Box / Vec的可变参考吗?

没有。这意味着,如果您将值存储在现有 Box中,则必须通过对数据的可变引用来执行此操作,例如使用Box::borrow_mut()。< / p>

本节试图传达的主要思想是,在对内容进行另一次引用时,您无法修改Box的内容。由于Box拥有其内容,因此保证了这一点。为了更改Box的内容,您必须通过采用新的可变引用来实现。

这意味着 - 即使你用较短的值覆盖内容 - 它也不重要,因为没有其他人可以使用旧值。借阅检查员不会允许它。

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

答案 1 :(得分:1)

来自nomicom

  

Box和Vec是有趣的案例,因为它们是变体,但你绝对可以在其中存储价值!这就是Rust变得非常聪明的地方:它们很适合变种,因为你只能通过一个可变引用来存储它们中的值!可变引用使整个类型不变,因此可以防止您将短期类型走私到它们中。

考虑使用Vec方法添加值:

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

自我的类型是&'a mut Vec<T>,我理解这是可变引用 nomicom所说的,所以为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)overwrite(&mut forever_str, &mut &*string)在nomicom示例中的相似性。

答案 2 :(得分:1)

自从在Nomicon回购中打开问题以来,维护者已经引入了revision to the section,我觉得这个问题相当清楚。修订已合并。我认为修改后回答了我的问题。

下面我提供我所知道的简要摘要。

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

  

BoxVec是有趣的案例,因为它们是协变的,但是你   绝对可以存储价值!这是Rust的类型系统   允许它比其他人更聪明。要理解它为什么   我们的声音是拥有容器对其内容的协变   必须考虑突变可能发生的两种方式:按值或按   通过引用

     

如果变异是按值,那么记住额外的旧位置   细节被移出,意味着它不能再使用该值。所以我们   根本不需要担心任何人记住危险的细节。   换句话说,在传递by-values时应用子类型会破坏   细节永远。例如,这编译并且很好:

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

如果变异是引用,那么我们的容器将作为&mut Vec<T>传递。但&mut对其来说是不变的   值,因此&mut Vec<T>实际上对T不变。所以这个事实   Vec<T>对T的协变在变异时根本不重要   通过引用

这里的关键点是&mut Vec<T>T之间的不变性与&mut T之间T的不变性之间的平行关系。

之前在修订后的nomicon部分解释了为什么一般&mut T不能在T上进行协变。 &mut T借用T,但它不拥有T,这意味着还有其他内容引用T并且对其生命周期有一定的期望。

但是,如果我们被允许在&mut T上传递T协变,那么nomicon示例中的overwrite函数就会显示我们如何打破T的生命周期来电者的位置来自不同的位置(即overwrite的正文内)。

从某种意义上说,允许类型构造函数的T协方差允许我们在传递类型构造函数时'忘记T'的原始生命周期,并且这'忘记了{{的原始生命周期1}}'对T没问题,因为我们没有机会通过它修改&T,但是当我们有T因为我们能够修改它时,这很危险 &mut T 忘记了有关它的生命周期详情。这就是T需要在&mut T上保持不变的原因。

似乎nomicon试图做的是:TBox<T>的协变是可以的,因为它不会引入不安全感。

这种协方差的一个后果是,我们被允许在按值传递T时忘记T'的原始生命周期。但这并不会带来不安全感,因为当我们通过值传递时,我们保证在Box<T>移出的位置没有T的其他用户。移动后,旧位置的其他任何人都没有指望Box<T>的前一生命期保持不变。

但更重要的是,TBox<T>的协变性并不会引起对T的可变引用的不安全性,因为Box<T>&mut Box<T>不变{1}}因此Box<T>不变。因此,与上面的T讨论类似,我们无法通过&mut T执行终生诡计,忘记了有关&mut Box<T>的有效期详细信息,然后再修改它。

答案 3 :(得分:0)

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