第一个问题:什么是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 可以解决什么问题?
- Keep track of what parts of code are using what data on the heap.
- Minmize the amount of duplicate data on the heap
- Clean up unused data on the heap so you don’t run out of space.
Owership 三条规则
- Each value in Rust has an owner.
- There can only be on onwer at a time.
- When the owner goes out scope, the value will be dropped.
解读:每一个值都必须有一个owner,那么这个owner就需要负责这个变量的生死存亡,在一个时刻,只会存在一个owner,这样以来,资源就不可能存在资源被多次释放的问题了。资源释放的时间,就是owner退出它的scope之时。这点类似 C++ 中的unique_ptr
,其资源的所有权只属于一个对象。
String 例子
String的可变性,如果不加mut,String的大小是编译时期就确定的,后续无法修改。
fn main() {
// let s = String::from("hello"); // NOK 这里返回时一个string literal,不能被修改
let mut s = String::from("hello");
s.push_str(", world!"); // append a literal to a String
println!("{}", s);
}
String 是在Heap上分配的内存,释放的时机,就是生命周期结束,比如退出大括号。Rust 会自动调用drop
函数去清理String的内存,这点非常类似C++的RAII机制。
fn main() {
{
let s = String::from("hello"); // Heap上分配的,只是不能修改
} // this scope is now over, and s is no longer valid 退出作用域释放
}
Ownership的Move
Rust 中的变量拷贝,都是shallow copy,不会复制原始数据,并且原始的变量在move之后,就失效了。
fn main() {
let x = 5;
let y = x; // 因为x是基础类型,所以x转移了ownership,x仍然是有效的。
}
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 此时就失效了,因为Onwer转移了。
println!("{}, world!", s1); // NOK,value borrowed here after move 错误。
}
S1 此时就失效了,内容的关系图。
用 clone 进行deep copy
这点和unique_ptr
不同,Rust 还默认提供了clone的功能,而C++就要使用clone的设计模式了。
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 居然可以拷贝一份
println!("s1 = {}, s2 = {}", s1, s2);
}
函数调用也会发生ownership转移
这点比较好理解,凡事进行赋值的地方,其实都会发生ownership的转移,函数也不例外。
fn main() {
let s = String::from("hello");
takes_ownership(s); // s的数据发生转移,s 失效。
}
fn takes_ownership(some_string: String) {
println!("{}", some_string); // 所有全被some_string拿走
} // some_string 退出scope释放资源
但是如果我不想s被夺走资源,后面还要继续用,该怎么办?
fn main() {
let s = String::from("hello");
// 通过函数返回值,再把资源返回出来
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s:String) -> (String, usize) {
let length = s.len();
(s, length) // 把s返回出去,那么资源就不会被释放
}
通过函数返回原先的资源,这种做法非常麻烦,如果很多资源要传递的话,代码就很糟糕了。