给定一个类型为Vec<&'static str>
的值,我可以自由地将其转换为Vec<&'r str>
,因为'r
是'static
的一个子区域。这似乎适用于大多数类型,例如Vec
、成对等但是,它不适用于Cell
或RefCell
等类型。具体来说,down_vec
编译,但down_cell
不会:
use std::cell::Cell;
fn down_vec<'p, 'r>(x: &'p Vec<&'static str>) -> &'p Vec<&'r str> {
x
}
fn down_cell<'p, 'r>(x: &'p Cell<&'static str>) -> &'p Cell<&'r str> {
x
}
给出错误:
error[E0308]: mismatched types
--> src/lib.rs:9:5
|
9 | x
| ^ lifetime mismatch
|
= note: expected reference `&'p std::cell::Cell<&'r str>`
found reference `&'p std::cell::Cell<&'static str>`
note: the lifetime `'r` as defined on the function body at 8:18...
--> src/lib.rs:8:18
|
8 | fn down_cell<'p, 'r>(x: &'p Cell<&'static str>) -> &'p Cell<&'r str> {
| ^^
= note: ...does not necessarily outlive the static lifetime
为什么这对Cell
不起作用?编译器如何跟踪它不起作用?有没有其他方法可以使其工作?
Cell
和RefCell
是不同的,因为它们允许通过共享引用改变内部值。
为了了解为什么这很重要,我们可以编写一个函数,该函数使用down_cell
泄漏对释放内存的引用:
fn oops() -> &'static str {
let cell = Cell::new("this string doesn't matter");
let local = String::from("this string is local to oops");
let broken = down_cell(&cell); // use our broken function to rescope the Cell
broken.set(&local); // use the rescoped Cell to mutate `cell`
cell.into_inner() // return a reference to `local`
} // uh-oh! `local` is dropped here
oops
不包含unsafe
块,但它可以编译,因此为了防止访问释放的内存,编译器必须拒绝down_cell
。
类型级解释为什么会这样,是因为Cell<T>
和RefCell<T>
包含UnsafeCell<T>
,这使得它们在T
中不变,而Box<T>
和Vec<T>
在T
中是协变的。
Vec
、Box
和其他类似容器的结构之所以可以协变,是因为这些容器需要&mut
访问才能改变它们的内容,而&mut T
本身在T
中是不变的。你不能用down_vec
来写一个像oops
这样的函数——编译器不允许这样做。
引用
- Rustonomicon 的子类型和方差章节
- Rust 编译器如何知道 'Cell' 具有内部可变性?
- 为什么链接生存期只对可变引用很重要?