fn works<'a>(foo: &Option<&'a mut String>, s: &'a mut String) {}
fn error<'a>(foo: &RefCell<Option<&'a mut String>>, s: &'a mut String) {}
let mut s = "hi".to_string();
let foo = None;
works(&foo, &mut s);
// with this, it errors
// let bar = RefCell::new(None);
// error(&bar, &mut s);
s.len();
如果我在注释中输入两行,会出现以下错误:
error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable
--> <anon>:16:5
|
14 | error(&bar, &mut s);
| - mutable borrow occurs here
15 |
16 | s.len();
| ^ immutable borrow occurs here
17 | }
| - mutable borrow ends here
works()
和 errors()
的签名看起来非常相似。但显然编译器知道您可以使用 RefCell
作弊,因为借用检查器的行为不同。
我什至可以在我自己的另一种类型中“隐藏”RefCell
,但编译器仍然总是做正确的事情(如果可以使用 RefCell
会出错).编译器如何知道所有这些东西以及它是如何工作的?编译器是否将类型标记为“内部可变性容器”或类似的东西?
最佳答案
RefCell<T>
包含 UnsafeCell<T>
这是一个特殊的lang item .是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
是invariant在 T. 事实上,我们可以使用 PhantomData 重现错误:
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
是Contravariant<&'x i32>
. error
函数接受Contravariant<&'a i32>
的任何子类型, 其中'a
是那个&mut s
的生命周期表达。- 因此
bar
应该是Contravariant<&'a i32>
的子类型 -
Contravariant<T>
在T
上是逆变的,即如果U <: T
, 然后Contravariant<T> <: Contravariant<U>
. - 所以当
&'x i32
时子类型关系可以满足是&'a i32
的父类(super class)型 . - 因此
'x
应该比'a
短 ,即'a
应该活'x
.
同样,对于不变类型,派生关系是'a == 'x
,对于协变,'x
超过生命周期'a
.
现在,这里的问题是 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()
必须包含在范围内,导致 borrowck 错误。
只有在协变的情况下,我们才能使范围为 '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 -------+
关于rust - 这个错误是由于编译器对 RefCell 的特殊了解吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43210387/