&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?

  1. 优化内存大小,NonNull 可以 Null pointer optimization,size_of::<NonNull<i64>>() == size_of::<Option<NonNull<i64>>>()
  2. 类型需要提供协变

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, 将无法编译,这看上去才是我们期望的行为。

问题是:

  1. 为什么 MyCell 的代码能编译?
  2. MyCellCell 有和区别?

第一个问题,按照自定义类型的 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