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
则出错)。编译器如何知道所有这些东西以及它是如何工作的?编译器是否将类型标记为“内部可变容器”或类似内容?
答案 0 :(得分:8)
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引入内部可变性,而是T中的UnsafeCell
为invariant。事实上,我们可以使用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);
甚至是生命中{* 1}}
中任何逆变或不变的东西'a
您无法隐藏RefCell的原因是因为方差是通过结构的字段得出的。一旦你在某处使用fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}
let bar = None;
error(bar, &mut s);
,无论多深,编译器都会发现RefCell<T>
是不变的。
现在让我们看看编译器如何确定E0502错误。首先,重要的是要记住编译器必须在这里选择两个特定的生命周期:表达式T
(&mut s
)类型的生命周期和{{'a
类型的生命周期1}}(让我们称之为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
{/ 1}} <{1}} 同样,对于不变类型,派生关系为'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()
(意思是:at 'a
,'x
不再被视为借用了):
&mut s