这个错误是由于编译器对 RefCell 的特殊了解吗?
Is this error due to the compiler's special knowledge about RefCell?
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
作弊,因为借用检查器的行为不同。
我什至可以 "hide" 我自己的另一种类型的 RefCell
,但编译器仍然总是做正确的事情(如果可以使用 RefCell
则出错)。编译器如何知道所有这些东西以及它是如何工作的?编译器是否将类型标记为 "interior mutability container" 或类似的东西?
RefCell<T>
包含一个 UnsafeCell<T>
which is a special 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 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
:
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
(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
,对于协变,'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
outlives (or equals to) 'x
表示语句 s.len()
必须包含在范围内,导致 borrowck 错误。
只有在协变的情况下,我们才能使 'a
的范围小于 'x
,从而允许在调用 s.len()
之前删除临时对象 &mut s
(意思是: 在 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 -------+
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
作弊,因为借用检查器的行为不同。
我什至可以 "hide" 我自己的另一种类型的 RefCell
,但编译器仍然总是做正确的事情(如果可以使用 RefCell
则出错)。编译器如何知道所有这些东西以及它是如何工作的?编译器是否将类型标记为 "interior mutability container" 或类似的东西?
RefCell<T>
包含一个 UnsafeCell<T>
which is a special 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 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
:
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
(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
,对于协变,'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
outlives (or equals to) 'x
表示语句 s.len()
必须包含在范围内,导致 borrowck 错误。
只有在协变的情况下,我们才能使 'a
的范围小于 'x
,从而允许在调用 s.len()
之前删除临时对象 &mut s
(意思是: 在 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 -------+