提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
Rust
的所有权概念是其最独特的特性之一,它对Rust
的其它部分也有很大影响。所有权概念让Rust
在没有GC的情况下,也能够保证内存安全(无需手动管理内存C
和C++
)。
GC就是garbage collection,比较直观,
Java
、Go
等均需要。
内存安全广义讲应该包括没有野指针、内存重复释放等错误。进一步的,在rust
中结合其它特性可以实现多线程的并发安全访问,C
和C++
常见的内存泄露则不属于不安全(虽然也是问题)。
一、Ownership是什么?
概括的讲,所有权是rust管理内存的一个规则集。
众所周知,计算机程序都需要管理内存,只是不同的编程语言采用了不同的方法。主要分两类:
- 不断检测未使用内存并释放的GC(Java/go…)
- 程序员手工管理内存(c/c++…)
而Rust采用第三种方法:通过一个包含规则集的所有权系统来管理内存。编译器会检查程序的内存使用是否符合规则集中的规则,违反规则的程序将无法编译通过。另外,这些规则的校验不会带来执行期的开销(编译期检查,编译很慢)。
规则有没有存在漏洞的可能性?导致内存不安全呢?目前看还没有
虽然对很多人来讲,所有权是一个新概念,需要时间来适应。但是,理解所有权是理解Rust独特性的基础。
二、规则集
所有权的规则集主要包括三条,可以类比C++
中的RAII
、unique_ptr
等概念。
- 每个值都有一个称之为owner的变量(有点类似
C++
new完必须记得delete) - 任一时刻,每个value只有一个owner(即不可共享,实际多线程是可以共享的,不过需要用到
unsafe
等特性) - 程序执行出了owner作用域,对应的值会被丢弃(类似
C++
自动调用析构函数)
三、理论+示例
a. 简单情况
{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid
以上为例,当s的作用域结束之时,即可以将s拥有的值所占用的堆(heap)空间释放。这个机制与C++的RAII( Resource Acquisition Is Initialization)类似,Rust通过调用drop
,而C++调用析构函数。
b. 复杂情况
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
对于涉及到堆内存的对象,Rust默认采用浅复制。此外,s2 = s1
执行之后,在rust
中,s1将不再指向任何String对象,s2单独拥有字符串hello
。图中的情况,其实更符合C++
中的shared_ptr
赋值后(引用计数增加)。
解决了问题,和C++中的
unique_ptr
类似,shared_ptr的需求通过Rc
、Arc
等特性解决。
深复制需要显式声明:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
}
上述s1.clone()
中的clone
方法即是深拷贝,当然前提和C++
一样,你得正确实现相应的函数,rust
中就是Clone
trait。
Copy trait? 有什么意义吗?因为所有类型的对象都可以copy吧?
函数调用的实参,函数返回值,均会转移对象的所有权。
四、引用和借用
引用并没有对应值的所有权,因此不会触发调用drop。引用可以成为借用。
多个变量指向同一个对象,没有共享问题?单线程下不会,多线程下则不允许。
同一个对象不能同时被借用多次,下面代码编译错误。
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
这个限制可以在编译期消除数据竞争。
但是,对象已经可以被两个变量修改了啊?可变引用变量+原来的变量,这两者的竞争如何解决呢,多线程不允许共享,如果需要共享,则需要用其它机制。
不可变引用可以有多个,但是可变引用只能有一个。当不可变引用完全无用之前,不能有新的可变引用。(所谓的共享不可变,可变不共享)
编译期检测虚悬引用(dangling)
引用规则:
- 任一时刻,只能由一个可变引用、或者若干不可变引用
- 引用必须一直有效
五、切片类型(slice)
避免下面这种问题,s.clear()
之后,s[word]
访问其实已经非法,运行期报错。
thread ‘main’ panicked at ‘byte index 5 is out of bounds of ``’, src/main.rs:20:18
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word will get the value 5
s.clear(); // this empties the String, making it equal to ""
// word still has the value 5 here, but there's no more string that
// we could meaningfully use the value 5 with. word is now totally invalid!
}
总结
初看下来,Rust实现内存安全的机制,并没有什么高深的。其实就是可变不共享,共享不可变?