Rust 所有权借用机制
我用裸指针三十年,一共也就崩掉了三(sán)万多个程序。YEAH!
简介
Rust 是一个由 Mozilla 开发的,准则为 「安全、并发、实用」的编程语言。
其实就是一个带指针安全的现代版 C++ (大雾)。
Rust 的设计不允许空指针和悬空指针,如果有就会编译失败。
Rust 有一个检查指针生命期间和指针冻结的系统,可以用来预防在 C++ 中许多的类型错误,甚至是用了智能指针功能之后会发生的类型错误。
C++ 看了都想打人
先来看一段 Rust 的代码
1 | let v = vec![1, 2, 3]; |
虽然语法跟 C++ 有区别,不过语义是类似的——声明了两个变量,并打印第一个变量中的一部分内容。
如果写成 C++ 这样的代码习以为常。
但是这段代码在 Rust 中会编译失败。
1 | error: use of moved value: `v` |
所有权
在 Rust 中有一个所有权的概念。失去了所有权,将不能进行读取或者修改操作。
变量绑定(变量声明,函数调用等)会获得所有权。
所有权跟作用域绑定(通常是大括号)。离开作用域后,会自动释放资源。
Rust 中也有移动语义,如果将一个变量直接赋值给另一个变量,假设没有实现 Copy trait ,所有权会被移动到新的变量。
所以上面的报错信息
1 | error: use of moved value: `v` |
说的是使用了移动过的变量 v
。一旦变量被移动过了,就禁止使用,就算 v
能指向正确的内容。
这么做是有原因的。
假设允许编译通过,可能会出现这样危险的代码
1 | let v = vec![1, 2, 3]; |
这是因为修改了内容而原来的指针并不知道,所以按照原来看起来正确的方式最终导致了错误。
借用
有时候所有权转义会带来麻烦
1 | fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) { |
其实只是需要内容而不是所有权啊。
所以 Rust 还有借用的机制。
使用借用机制后是这样的
1 | fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 { |
使用了借用机制之后没有所有权转移,自然就不用归还所有权了。
上面的引用是常数引用,不能修改。想修改需要使用 mut
关键字
1 | let mut x = 5; |
借用机制类似与 C++ 的引用机制但是又有几个区别。
- 可以有多个常数级借用(&T)
- 只能有一个可修改借用 (&mut T)
而且一个资源只能有一种借用方式,要么是常数级借用,要么是可修改借用。
借用也是跟作用域绑定的。比如以下代码
1 | fn main() { |
会编译失败
1 | error: cannot borrow `x` as immutable because it is also borrowed as mutable |
这是因为 x 已经借用给 y 当作可修改借用。然后 println!
传递参数的时候需要常数级借用,前面说过一个资源只能有一种借用方式。
如果改成这样
1 | fn main() { |
就可以了。因为 y 出了借用的作用域,借用就会自动归还。后面不会出现借用冲突的问题。
走开!烦人的空悬指针
空悬指针(dangling pointer)通常是由释放后使用(use after free)产生的。
Rust 的借用机制会防止释放后使用发生。
1 | let y: &i32; |
编译报错
1 | error: `x` does not live long enough |
错误提示已经说得很清楚了。就是 x
苟活的时间不够长。因为 x
出了作用域后就不再可用,所以 y
也相应的不再可用。因此拒绝编译通过可以避免后面的释放后使用的问题。
最后
Rust 的空指针的解决方案是 option type 。这里不再赘述了。
天下没有免费的午餐。Rust 又要高性能,还要零负担抽象(诶怎么说得那么像某语言),那么就只能付出用户心智负担和编译时间的代价了。
从源头上避免空指针和空悬指针是可以做到的,如果你还觉得不可能是因为你用的不是现代编程语言啊,科科!