Rust Nomicon有an entire section on variance这个我或多或少都能理解,除了Box<T>
和Vec<T>
与T
之间的(共)变体这个小部分。
Box
和Vec
是有趣的案例,因为它们是变体,但您绝对可以在其中存储值!这就是Rust变得非常聪明的地方:它们很适合变种,因为你只能通过可变引用将值存储在它们中!可变引用使整个类型不变,因此可以防止您将短期类型走私到它们中。
令我困惑的是以下几行:
它们很适合变种,因为你只能通过可变引用来存储中的值!
我的第一个问题是,我对可变引用的含义感到有些困惑。它是Box
/ Vec
的可变引用吗?
如果是这样的话,我只能通过可变引用在其中存储值的事实如何证明它们的(共)方差?我理解(共)方差是什么以及将它用于Box<T>
,Vec<T>
等的好处,但我很难看到只能通过可变引用存储值与理由之间的联系(共)方差。
另外,当我们初始化Box
时,是否在没有涉及可变引用的情况下将值移入框中?这是否与我们只能通过可变引用存储值的声明相矛盾?
最后,在什么情况下借用这个“可变参考”?他们是否意味着当您调用修改Box
或Vec
的方法时,您隐含地使用&mut self
?这是提到的可变参考吗?
2018年5月2日更新:
由于我还没有收到这个问题的满意答案,我认为这个nomicon的解释真的令人困惑。正如下面的评论帖子中所承诺的那样,我已经打开了an issue in the Rust Nomicon repository。您可以跟踪那里的任何更新。
答案 0 :(得分:2)
我认为该部分可以使用一些工作来使其更清晰。
我对可变引用的含义感到有些困惑。它是Box / Vec的可变参考吗?
没有。这意味着,如果您将值存储在现有 Box
中,则必须通过对数据的可变引用来执行此操作,例如使用Box::borrow_mut()
。< / p>
本节试图传达的主要思想是,在对内容进行另一次引用时,您无法修改Box
的内容。由于Box
拥有其内容,因此保证了这一点。为了更改Box
的内容,您必须通过采用新的可变引用来实现。
这意味着 - 即使你用较短的值覆盖内容 - 它也不重要,因为没有其他人可以使用旧值。借阅检查员不会允许它。
这与函数参数不同,因为函数有一个代码块,它实际上可以用它的参数做事。在Box
或Vec
的情况下,您必须通过可变地借用它们来获取内容,然后才能对它们执行任何操作。
答案 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。
以另一种方式说:Vec
和Box
包含的值始终比其容器更长,尽管Vec
和Box
是变体,因为您只能通过它存储值一个可变的参考。
请考虑以下代码段:
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,我觉得这个问题相当清楚。修订已合并。我认为修改后回答了我的问题。
下面我提供我所知道的简要摘要。
与我的问题相关的部分现在如下(强调我的):
Box
和Vec
是有趣的案例,因为它们是协变的,但是你 绝对可以存储价值!这是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试图做的是:T
对Box<T>
的协变是可以的,因为它不会引入不安全感。
这种协方差的一个后果是,我们被允许在按值传递T
时忘记T
'的原始生命周期。但这并不会带来不安全感,因为当我们通过值传递时,我们保证在Box<T>
移出的位置没有T
的其他用户。移动后,旧位置的其他任何人都没有指望Box<T>
的前一生命期保持不变。
但更重要的是,T
对Box<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
是不变的。)