&mutT
invariance
1
2
3
4
5
6
7
8
9
10
11
12
13
| fn evil_feeder<T>(input: &mut T, val: T) {
*input = val;
}
fn main() {
let mut mr_snuggles: &'static str = "meow! :3"; // mr. snuggles forever!!
{
let spike = String::from("bark! >:V");
let spike_str: &str = &spike; // Only lives for the block
evil_feeder(&mut mr_snuggles, spike_str); // EVIL!
}
println!("{}", mr_snuggles); // Use after free?
}
|
1
2
3
4
5
6
7
8
9
10
11
| error[E0597]: `spike` does not live long enough
--> src/main.rs:9:31
|
6 | let mut mr_snuggles: &'static str = "meow! :3"; // mr. snuggles forever!!
| ------------ type annotation requires that `spike` is borrowed for `'static`
...
9 | let spike_str: &str = &spike; // Only lives for the block
| ^^^^^^ borrowed value does not live long enough
10 | evil_feeder(&mut mr_snuggles, spike_str); // EVIL!
11 | }
| - `spike` dropped here while still borrowed
|
由于&mutT
invariance, 因此 T 一定被编译器推导为 &mut &'static str
, 要使 evil_feeder 函数编译,T 的类型也只能是 &‘static str,显然不成立。
NonNull<T>
协变
不同于 &mut T
, NoneNull<T>
是协变的,在使用时,必须要由使用者保证不会将指针指向更短生命周期的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| fn test_non_null() {
fn overwrite<'a>(mut _big_str: NonNull<&'a str>, _small_str: &&'a str) {
unsafe {*_big_str.as_mut() = *_small_str;}
}
let hello = "hello";
let mut big_str: &'static str = hello;
'small: {
let small_str = String::from("world");
let p1: NonNull<&'static str> = NonNull::new(&mut big_str).unwrap();
overwrite(p1, &&*small_str);
}
println!("output: {}", big_str);
}
// 并不会输出 wolrd
|
什么时候使用 NonNull
?
- 优化内存大小,NonNull 可以 Null pointer optimization,
size_of::<NonNull<i64>>() == size_of::<Option<NonNull<i64>>>()
- 类型需要提供协变
Cell invariance
如下的代码能正常编译,但结果如我们所料,是一个未定义的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| struct MyCell<T> {
value: T
}
impl<T: Copy> MyCell<T> {
fn new(value: T) -> Self {
MyCell { value }
}
fn set(&self, new_value: T) {
unsafe { std::ptr::write(&self.value as *const T as *mut T, new_value);
}
}
fn foo(rcell: &MyCell<&i32>) {
let val: i32 = 13;
rcell.set(&val);
println!("foo set value: {}", rcell.value);
}
fn main() {
static X: i32 = 10;
let cell = MyCell::new(&X);
foo(&cell);
println!("end value: {}", cell.value);
}
// foo set value: 13
// end value: 0
|
如果将 MyCell
替换成 Cell
, 将无法编译,这看上去才是我们期望的行为。
问题是:
- 为什么
MyCell
的代码能编译? MyCell
与 Cell
有和区别?
第一个问题,按照自定义类型的 variance,MyCell<T>
协变于 T。假设 val 的生命周期为 ‘a, cell 的生命周期为 ‘b,显然 ‘b > ‘a。
调用 set 函数时,‘a Self 可以协变为 ‘b Self, 故能够正常编译(self 参数协变)。
在设计 Cell
类型时,由于可以改变内部值,需要将 variance 限制为 invariance, 避免暴露的接口出现未定义行为。
1
2
3
4
| struct MyCell<T> {
value: T,
_marker: PhantomData<Cell<T>>,
}
|
第二个问题,unsafe_cell
由编译器内部实现了 invariance
。
1
2
3
4
5
6
7
| pub struct Cell<T: ?Sized> {
value: UnsafeCell<T>,
}
#[lang = "unsafe_cell"] // --> known to the compiler
pub struct UnsafeCell<T: ?Sized> {
value: T,
}
|
referrences