RefCell<T>
包含一个UnsafeCell<T> https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html这是一个特殊的语言项 https://doc.rust-lang.org/unstable-book/language-features/lang-items.html. It is UnsafeCell
这会导致错误。您可以检查:
fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {}
...
let bar = UnsafeCell::new(None);
error(&bar, &mut s);
但该错误并不是由于编译器识别出 UnsafeCell 引入了内部可变性,而是由于UnsafeCell
is 不变的 https://doc.rust-lang.org/nomicon/subtyping.html事实上,我们可以使用以下命令重现该错误幻象数据 https://doc.rust-lang.org/std/marker/struct.PhantomData.html:
struct Contravariant<T>(PhantomData<fn(T)>);
fn error<'a>(foo: Contravariant<&'a i32>, s: &'a mut String) {}
...
let bar = Contravariant(PhantomData);
error(bar, &mut s);
甚至是一生中逆变或不变的任何事物'a
:
fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}
let bar = None;
error(bar, &mut s);
无法隐藏 RefCell 的原因是方差是通过结构的字段导出的。一旦你使用过RefCell<T>
某个地方,无论多深,编译器都会弄清楚T
是不变的。
现在让我们看看编译器如何判断E0502错误。首先,重要的是要记住编译器必须在这里选择两个特定的生命周期:表达式类型的生命周期&mut s
('a
)和类型的生命周期bar
(我们称之为'x
)。两者都受到限制:前一世'a
必须短于范围s
,否则我们最终会得到一个比原始字符串寿命更长的引用。'x
必须大于范围bar
,否则我们可以通过访问悬空指针bar
(如果类型具有生命周期参数,则编译器假定该类型可以访问具有该生命周期的值)。
有了这两个基本限制,编译器将执行以下步骤:
- 方式
bar
is Contravariant<&'x i32>
.
- The
error
函数接受任何子类型Contravariant<&'a i32>
, where 'a
是那个的生命周期&mut s
表达。
- Thus
bar
应该是一个子类型Contravariant<&'a i32>
-
Contravariant<T>
是逆变的T
,即如果U <: T
, then Contravariant<T> <: Contravariant<U>
.
- 因此子类型关系可以满足
&'x i32
is a 超型 of &'a i32
.
- Thus
'x
应该shorter than 'a
, i.e. 'a
should outlive 'x
.
类似地,对于不变类型,派生关系是'a == 'x
,对于协变,'x
比寿命长'a
.
现在,这里的问题是type of bar
生存到范围结束(根据上述限制):
let bar = Contravariant(PhantomData); // <--- 'x starts here -----+
error(bar, // |
&mut s); // <- 'a starts here ---+ |
s.len(); // | |
// <--- 'x ends here¹ --+---+
// |
// <--- 'a ends here² --+
}
// ¹ when `bar` goes out of scope
// ² 'a has to outlive 'x
在逆变和不变的情况下,'a
寿命长(或等于)'x
意味着声明s.len()
必须包含在范围内,导致借用错误。
只有在协变的情况下,我们才能使范围'a
比。。。短'x
,允许临时对象&mut s
之前被删除s.len()
被称为(意思是:在s.len()
, s
不再被视为借用):
let bar = Covariant(PhantomData); // <--- 'x starts here -----+
// |
error(bar, // |
&mut s); // <- 'a starts here --+ |
// | |
// <- 'a ends here ----+ |
s.len(); // |
} // <--- 'x ends here -------+