第一个问题:什么是Ownership?

Ownership is a set of rules that governs how a Rust program manages memory. 就是内存或者资源管理的规则。比如,Java 有garbage colletion,C++有RAII等。

Rust 关心 the Stack 和 the Heap 内存,不同的分配方式,Rust的行为也是不一样的。

堆栈基础的特点回顾

Stack 内存的特点需要注意下:1、资源FIFO,先分配的后释放;2、All data stored on the stack must have a know fixed size. 固定大小。

Heap 上分配内存的术语是:allocating on the heap,在heap上分配好内存,然后在stack上用一个pointer指向它即可。heap上分配内存性能慢是因为要去找一块big enough的space,而stack直接在the top of the stack 上分配就好了。在heap上access数据也慢,因为多了一层指针寻址。

Ownership 可以解决什么问题?

  1. Keep track of what parts of code are using what data on the heap.
  2. Minmize the amount of duplicate data on the heap
  3. Clean up unused data on the heap so you don’t run out of space.

Owership 三条规则

  1. Each value in Rust has an owner.
  2. There can only be on onwer at a time.
  3. When the owner goes out scope, the value will be dropped.

解读:每一个值都必须有一个owner,那么这个owner就需要负责这个变量的生死存亡,在一个时刻,只会存在一个owner,这样以来,资源就不可能存在资源被多次释放的问题了。资源释放的时间,就是owner退出它的scope之时。这点类似 C++ 中的unique_ptr,其资源的所有权只属于一个对象。

String 例子

String的可变性,如果不加mut,String的大小是编译时期就确定的,后续无法修改。

  1. fn main() {
  2. // let s = String::from("hello"); // NOK 这里返回时一个string literal,不能被修改
  3. let mut s = String::from("hello");
  4. s.push_str(", world!"); // append a literal to a String
  5. println!("{}", s);
  6. }

String 是在Heap上分配的内存,释放的时机,就是生命周期结束,比如退出大括号。Rust 会自动调用drop 函数去清理String的内存,这点非常类似C++的RAII机制。

  1. fn main() {
  2. {
  3. let s = String::from("hello"); // Heap上分配的,只是不能修改
  4. } // this scope is now over, and s is no longer valid 退出作用域释放
  5. }

Ownership的Move

Rust 中的变量拷贝,都是shallow copy,不会复制原始数据,并且原始的变量在move之后,就失效了。

  1. fn main() {
  2. let x = 5;
  3. let y = x; // 因为x是基础类型,所以x转移了ownership,x仍然是有效的。
  4. }
  5. fn main() {
  6. let s1 = String::from("hello");
  7. let s2 = s1; // s1 此时就失效了,因为Onwer转移了。
  8. println!("{}, world!", s1); // NOK,value borrowed here after move 错误。
  9. }

4.1 What is Ownership? - 图1

S1 此时就失效了,内容的关系图。

用 clone 进行deep copy

这点和unique_ptr 不同,Rust 还默认提供了clone的功能,而C++就要使用clone的设计模式了。

  1. fn main() {
  2. let s1 = String::from("hello");
  3. let s2 = s1.clone(); // 居然可以拷贝一份
  4. println!("s1 = {}, s2 = {}", s1, s2);
  5. }

函数调用也会发生ownership转移

这点比较好理解,凡事进行赋值的地方,其实都会发生ownership的转移,函数也不例外。

  1. fn main() {
  2. let s = String::from("hello");
  3. takes_ownership(s); // s的数据发生转移,s 失效。
  4. }
  5. fn takes_ownership(some_string: String) {
  6. println!("{}", some_string); // 所有全被some_string拿走
  7. } // some_string 退出scope释放资源

但是如果我不想s被夺走资源,后面还要继续用,该怎么办?

  1. fn main() {
  2. let s = String::from("hello");
  3. // 通过函数返回值,再把资源返回出来
  4. let (s2, len) = calculate_length(s1);
  5. println!("The length of '{}' is {}.", s2, len);
  6. }
  7. fn calculate_length(s:String) -> (String, usize) {
  8. let length = s.len();
  9. (s, length) // 把s返回出去,那么资源就不会被释放
  10. }

通过函数返回原先的资源,这种做法非常麻烦,如果很多资源要传递的话,代码就很糟糕了。