Rust 中的型变
协变在Rust中是一个今人迷惑的话题。对于新手可能感知不到这些概念。本篇内容将给出一个更通俗的理解。
首先, 有两个重要的点我们要关注一下。
- 变性是一个与通用参数T有关的概念
- Rust的生命周期参数('a)具有子类型化的设计
?关于子类型是什么,如果你学习过其它高级语言,就很容易理解。
?关于生命周期,你可以理解为一个变量在栈上什么时候有效,没被释放。更通俗的来说。 就是 一个作用域
,代码块
。
Rust中的型变就是允许对一个生命周期参数进行如何的变化:
注意:变性这些概念实际上是我们需要做的理论假设, 这样我们才能确保我们的实现是正确的。不然, 程序会炸,出现各种无效指针。
在Rust中无论你在哪里看到变性,默认情况下,它表示协变。
现在这里有一个经典的例子。
先试着猜测输出结果:
struct MyCell<T> {
value: T
}
impl<T> MyCell<T> {
fn new(value: T) -> Self {
MyCell { value }
}
fn get(&self) -> &T {
&self.value
}
fn set(&self, new_value: T) {
// 注意这里,方法获取的是一个不可变的实例(&self)不是&mut self。如果你改成了。那就不会
// 有问题了。 编译时会提示new_value生命周期不足
// 我们需要使用不安全的内存写入方法,制造这个'BUG'
unsafe {
std::ptr::write(&self.value as *const _ as *mut T, new_value);
}
}
}
fn set_value(source: &MyCell<&i32>) {
println!("旧的值是:{}", source.get());
let bugvar = 100;
source.set(&bugvar);
}
fn main() {
let a= MyCell::new(&10);
set_value(&a);
println!("现在的值是:{}", a.get());
}
// 最终输出:
// 旧的值是:10
// 现在的值是:-65374792
Rust忽然变的内存不安全了。 造成这种原因的。就是今天所说的 变性。
我们展开一下类似编译的MIR风格代码解析一下问题所在。
'a: {
let a = MyCell::new(&'a 10);
'b: {
let bugvar: i32 = 100; // <-- 这个BUG变量创建在这里
'c: {
'd {
source.set(&'d bugvar);
}
}
// <-- bufvar 释放在这里
}
println!("end value: {}", cell.value);
}
问题的根源在于,我们允许 改变
了MyCell里包裹的val。
(重点:这种关系,MyCell对于val来说。是协变*)
我们把生命周期只有'b范围的变量bugvar,强行塞进了生命周期比它更长的'a范围里。那么一但较短的那个离开了作用域。那么更长的变量却保存着他。 结果就是: 炸 指向了一个无效的地址。所以,这就相当于, 我借了100万给你, 1个月后。 你竟然说是我欠了你100万。你说会不会炸? (你不经我同意单方面修改了内部的值)
这段代码的生命周期关系:a > b > c > d
- 划重点,如果要让MyCell包裹的值有效,那么必须裹入一个至少同样
>=a
的作用域
那如何告诉编译器,对MyCell做一些限制呢。
先参考Cell源码,核心在于多了一条#[lang = "unsafe_cell"]
编译器会认为这是一个不安全的块,限制你传入的参数生命周期必须和Cell一样或更长,否则 编译报错。不过
#[lang = "unsafe_cell"]
这条属性并不对外开放,语言内部使用, 你只有通过其它方式标记我们的MyCell了
pub struct Cell<T: ?Sized> {
value: UnsafeCell<T>,
}
#[lang = "unsafe_cell"] // --> 只是比我们写的MyCell多了一条这个属性
pub struct UnsafeCell<T: ?Sized> {
value: T,
}
在标准库中,有一个叫幽灵数据PhantomData<T>
,它用于模拟另一个类型,Rust在编译时会检查这个标志,让MyCell模拟一个特性
这里MyCell模拟了UnsafeCell。变相加上了 #[lang = "unsafe_cell"]
这条属性让MyCell的变性保持不变。到此。你编译时就得到一个生命周期不足的错误,自己动手试试吧。
变性 整体上来说。 就是一个容器与被包裹值的一种关系。
变性只是一个概念,不要过度理解造成负担,你可以想成其实啥也不是,就是一种我们假设的理论性规范, 遵守它, 程序才能正确的运行。不遵守。程序就是一个字: 炸
变性还有逆变与不变,之后有机会将继续