0. Intro
最近考虑自己项目用Rust重写的可能性,所以这里记录一下Rust的特性,以防止自己忘记。
Rust上手曲线有点陡峭,因为它和其它语言的设计思路很不一样。
这里就记录一些Rust特有的特性,首先就是“所有权”问题(想起了C++的std::move
)
Rust没有GC,又不像C/C++一样需要手动释放内存(这里我们只讨论堆内存),所以有了所有权概念。
假如有一个变量s
,它指向堆内的一块内存(假如是字符串),则:
- 当变量
s
在作用域内,则其有指向内容的所有权 - 当变量
s
在作用域外,则其没有指向内存的所有权,且指向的内容会被释放
1
2
3
4
5
6
7
fn ownership() {
{
let s = String::from("str"); // Create s -> "str" (in heap), s owns "str"
// ...
}
// Here s doesn't own "str", and "str" is freed
}
1. 移动与拷贝
1.1. 移动
Rust和其它语言不同——赋值默认是移动语义。
移动之后,所有权被移交出去,之前的变量就不可访问了:
1
2
3
let s = String::from("str");
let p = s; // Ownership moved from s to p
println!("{}", s); // Compiling error!
1.2. 拷贝
当数据结构实现了Copy
的trait(即数据结构可存储在栈上)后,其赋值默认就是拷贝,例如整数、字符、浮点数、布尔值。
1
2
3
let s = 1;
let p = s; // Value 1 is copied to p (copied in compilation time)
println!("{}", s); // OK
实现
Copy
后,则不能实现Drop
2. 函数与所有权
函数的参数传递和结果返回,也满足1.1.的语义,即:
- 默认移动语义
- 实现
Copy
默认拷贝
例如一般情况(内容存在堆中):
1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let v = String::from("str"); // v owns "str"
let q = move_ownership(v); // 1. v moves "str" to arg
println!("{}", q); // OK. q owns "str"
println!("{}", v); // Error, v doesn't own "str"
}
fn move_ownership(arg: String) -> String {
let p = arg; // 2. arg moves "str" to p
return p; // 3. p moves "str" to q
}
而栈中的数据,上述类似的调用是拷贝语义,编译不会出错。
3. 引用
为了让1.2.的情况编译通过,Rust引入引用概念,意思就是“我能使用它,但我不拥有它”,类似于借用的概念。
引用和C++一样,使用&
;而解引用和C++指针类似,使用*
。
当然,引用也分可变和不可变:
- 不可变引用:默认,使用
&
,引用只读 - 可变引用:使用
&mut
,引用可写
1
2
3
4
5
6
7
8
9
10
11
fn main() {
let v = String::from("str"); // v owns "str"
let p = reference(&v);// "str" is borrowed/referenced by arg, but v still owns it
println!("{}", v); // OK, v still owns "str"
println!("{}", p);
}
fn reference(arg: &String) -> usize {
// arg.push_str("append"); // Error: ref arg is immutable, cannot write
return arg.len();
}
1
2
3
4
5
6
7
8
9
fn main() {
let mut v = String::from("str"); // v owns "str"
reference_mut(&mut v); // "str" is borrowed/referenced by arg, but v still owns it
println!("{}", v);
}
fn reference_mut(arg: &mut String) {
arg.push_str("ssxsx"); // OK: arg is mutable, can write
}
但是引用的使用,需要注意下面几点:
-
不能通过解引用来移动:因为引用不拥有对应的内容,只有使用权。下面的例子会编译错误:
1 2 3
fn move_reference(arg: &String) -> String { return *arg; // Error: arg doesn't own the content, cannot move. }
-
不要返回悬垂引用:这会导致不可预计的后果。如:
- 源变量指向的内容被释放。如下面的例子,会编译错误:
1 2 3 4 5 6 7 8
fn main() { let ref: &String = dangling_ref(); // Error: ref is dangling, the content "str" is freed } fn dangling_ref() -> &String { let str = String::from("str"); // str owns "str" return &str; // Return the reference of str } // Function return, str doesn't own "str", "str" is freed.
- 源变量被移动。如下面的例子,也会编译错误
1 2 3 4 5 6
fn main() { let src = String::from("src"); // src owns "src" let ref = &src; // ref references to src let new_src = src; // src is moved to new_src println!("{}", ref); // Error: ref references src that is moved }
-
可变引用与不可变引用的冲突:Rust引用类似读写锁,在编译期就避免数据竞争,即一个变量不可以:
- 拥有多个可变引用
- 可变引用和不可变引用共存
4. Slice
Slice是集合中的一个部分,它是一种特殊且不可变的引用,没有所有权。
所以它不能移动,如不能
let s = *slice;
,这里slice
只是一个Slice/引用,只有使用权,不能获取所有权
Slice可作用于字符串、数组等数据结构,语法是:&arr[lower..upper]
。注意不要忘记&
,因为Slice是引用。
1
2
3
4
// example
let str = String::from("stringexample");
let slice = &str[0..4]; // A slice (reference) with content "stri"
let not_valid = *slice; // Error: Cannot move/dereference, because slice doesn't have ownerships
其中lower..upper
会返回一个Range
,lower
和upper
可省略。Range
左闭右开。
1
2
3
4
// example
for i in (1..10) {
println!("{}", i); // This will print 1 to 9
}
字面值也是Slice:
字符串的字面值也是一个Slice,它的类型是
&str
,即一个引用。所以,字面值是不能被修改,且不能被移动(因为接收字符串字面值的变量不拥有它的所有权)。