问题描述
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();
如果我把两行加上注释,就会出现如下错误:
If I put in the two lines with the comment, the following error occurs:
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
欺骗它,因为借用检查器的行为不同.
The signatures of works()
and errors()
look fairly similar. But apparently the compiler knows that you can cheat on it with a RefCell
, because the borrow checker behaves differently.
我什至可以在我自己的另一种类型中隐藏"RefCell
,但编译器仍然总是做正确的事情(如果可以使用RefCell
).编译器如何知道所有这些东西以及它是如何工作的?编译器是否将类型标记为内部可变性容器"或类似的东西?
I can even "hide" the RefCell
in another type of my own, but the compiler still always does the right thing (errors in case a RefCell
could be used). How does the compiler know all that stuff and how does it work? Does the compiler mark types as "interior mutability container" or something like that?
推荐答案
RefCell
包含一个 UnsafeCell
这是一个特殊的 语言项目.导致错误的是 UnsafeCell
.你可以检查:
RefCell<T>
contains an UnsafeCell<T>
which is a special lang item. It is UnsafeCell
that causes the error. You could check with:
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:
But the error is not due to compiler recognizing an UnsafeCell introduces interior mutability, but that an UnsafeCell
is invariant in T. In fact, we could reproduce the error using 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
:
or even just anything that is contravariant or invariant in the lifetime 'a
:
fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}
let bar = None;
error(bar, &mut s);
您不能隐藏 RefCell 的原因是因为方差是通过结构的字段派生的.一旦你在某处使用了 RefCell
,无论多深,编译器都会发现 T
是不变的.
The reason you can't hide a RefCell is because variance is derived through the fields of the structure. Once you used RefCell<T>
somewhere, no matter how deep, the compiler will figure out T
is invariant.
现在让我们看看编译器如何确定 E0502 错误.首先,重要的是要记住编译器必须在这里选择两个特定的生命周期:表达式类型中的生命周期 &mut s
('a
) 和生命周期在 bar
的类型中(我们称之为 'x
).两者都受到限制:前一个生命周期 'a
必须短于 s
的范围,否则我们最终会得到一个比原始字符串存活时间更长的引用.'x
必须大于 bar
的作用域,否则我们可以通过 bar
访问一个悬空指针(如果一个类型有生命周期参数编译器假定该类型可以访问具有该生命周期的值).
Now let's see how the compiler determine the E0502 error. First, it's important to remember that the compiler has to choose two specific lifetimes here: the lifetime in the type of the expression &mut s
('a
) and the lifetime in the type of bar
(let's call it 'x
). Both are restricted: the former lifetime 'a
has to be shorter than the scope of s
, otherwise we would end up with a reference living longer than the original string. 'x
has to be larger than the scope of bar
, otherwise we could access an dangling pointer through bar
(if a type has a lifetime parameter the compiler assume the type can access a value with that lifetime).
有了这两个基本限制,编译器会经历以下步骤:
With these two basic restriction, the compiler goes through the following steps:
- 类型
bar
是Contravariant
. error
函数接受Contravariant
的任何子类型,其中'a
是该的生命周期>&mut s
表达式.- 因此
bar
应该是Contravariant
的子类型 Contravariant
与T
是逆变的,即如果U ,则
Contravariant;
.- 所以当
&'x i32
是&'a i32
的超类型时,子类型关系可以得到满足. - 因此
'x
应该短比'a
,即'a
应该存活'x
.
- The type
bar
isContravariant<&'x i32>
. - The
error
function accepts any subtype ofContravariant<&'a i32>
, where'a
is the lifetime of that&mut s
expression. - Thus
bar
should be a subtype ofContravariant<&'a i32>
Contravariant<T>
is contravariant overT
, i.e. ifU <: T
, thenContravariant<T> <: Contravariant<U>
.- So the subtyping relation can be satisfied when
&'x i32
is a supertype of&'a i32
. - Thus
'x
should be shorter than'a
, i.e.'a
should outlive'x
.
同理,对于一个不变类型,派生关系是'a == 'x
,对于convariant,'x
比'a
存活>.
Similarly, for an invariant type, the derived relation is 'a == 'x
, and for convariant, 'x
outlives 'a
.
现在,这里的问题是 bar
的 type 的生命周期一直存在到作用域结束(根据上面提到的限制):
Now, the problem here is that the lifetime in the type of bar
lives until the end of scope (as per restriction mentioned above):
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错误.
In both contravariant and invariant cases, 'a
outlives (or equals to) 'x
means the statement s.len()
must be included in the range, causing borrowck error.
只有在协变的情况下,我们才能使'a
的范围比'x
短,允许临时对象&mut s
在 s.len()
被调用之前被删除(意思是:在 s.len()
处,s
不再被认为是借用的):
Only in the covariant case we could make the range of 'a
shorter than 'x
, allowing the temporary object &mut s
be dropped before s.len()
is called (meaning: at s.len()
, s
is not considered borrowed anymore):
let bar = Covariant(PhantomData); // <--- 'x starts here -----+
// |
error(bar, // |
&mut s); // <- 'a starts here --+ |
// | |
// <- 'a ends here ----+ |
s.len(); // |
} // <--- 'x ends here -------+
这篇关于这个错误是由于编译器对 RefCell 的特殊知识造成的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!