Luk*_*odt 10 rust borrow-checker
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();
Run Code Online (Sandbox Code Playgroud)
如果我在注释中添加两行,则会发生以下错误:
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
Run Code Online (Sandbox Code Playgroud)
签名works()和errors()外观相似.但显然编译器知道你可以用a欺骗它RefCell,因为借用检查器的行为不同.
我甚至可以"隐藏" RefCell我自己的另一种类型,但编译器仍然总是做正确的事情(RefCell如果可以使用的话可以使用错误).编译器如何知道所有这些东西以及它是如何工作的?编译器是否将类型标记为"内部可变容器"或类似的东西?
RefCell<T>包含UnsafeCell<T>一个特殊的郎项.这是UnsafeCell导致错误的原因.您可以查看:
fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {}
...
let bar = UnsafeCell::new(None);
error(&bar, &mut s);
Run Code Online (Sandbox Code Playgroud)
但错误是不是由于编译器识别的UnsafeCell引入内部可变性,但一个UnsafeCell是不变的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);
Run Code Online (Sandbox Code Playgroud)
甚至是生命中任何逆变或不变的东西'a:
fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}
let bar = None;
error(bar, &mut s);
Run Code Online (Sandbox Code Playgroud)
您无法隐藏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.'x应该短于'a,即'a应该比 寿命更长'x. 类似地,对于不变量类型,所导出的关系'a == 'x,以及用于convariant,'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
Run Code Online (Sandbox Code Playgroud)
在逆变和不变的情况下,'aoutlives(或等于)'x意味着语句s.len()必须包含在范围内,从而导致借入错误.
只有在协变的情况下,我们可以使范围'a更短'x,允许临时对象&mut s在s.len()被调用之前被丢弃(意思是:at 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 -------+
Run Code Online (Sandbox Code Playgroud)