自从在Nomicon存储库中提出问题以来,维护者已经引入对该部分的修订, 我认为这个修订更加清晰明了。修订已合并。我认为我的问题已经得到了回答。
下面我简要总结一下我的了解。
与我的问题相关的部分现在读起来如下所示(重点是我的):
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> {
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
并对其生命周期有一定的期望。但是,如果我们允许将
&mut T
协变于
T
,那么 nomicon 示例中的
overwrite
函数就展示了我们如何从一个不同的位置(即在
overwrite
的函数体内)打破调用者位置中
的 T
的生命周期。
在某种意义上,允许类型构造函数对
T
进行协变允许我们在传递类型构造函数时“忘记
T
的原始生命周期”,并且这种“忘记
T
的原始生命周期”对于
&T
是可以的,因为我们没有机会通过它来修改
T
,但是当我们有一个
&mut T
时,这就很危险了,因为我们有能力在“忘记关于它的生命周期细节之后”修改
T
。这就是为什么
&mut T
需要在
T
上不变的原因。
似乎nomicon试图表达的观点是:Box<T>对于
T
的协变是可以的,因为它不会引入不安全性。
这种协变的后果之一是,当按值传递
Box<T>
时,我们可以“忘记
T
的原始生命周期”。但这并不会引入不安全性,因为当我们按值传递时,我们保证在
Box<T>
被移动的位置上没有
T
的其他用户。旧位置中没有其他人指望
T
的先前寿命在移动后仍然存在。
更重要的是,Box<T>
在T
上的协变性不会在获取Box<T>
的可变引用时引入不安全性,因为&mut Box<T>
在Box<T>
上是不变的,因此在T
上也是不变的。因此,类似于上面关于&mut T
的讨论,我们无法通过忘记有关T
的寿命细节并在之后修改它来通过&mut Box<T>
执行生命周期操作。
&mut Box<T>
对于T
是不变的这一事实实际上防止了什么?例如,这段代码 将Box<&'a str>
中的&'a str
替换为&'static str
,它可以正常工作,但似乎这种情况应该被禁止,因为&mut T
对于T
是不变的。 - Michael Hewson