所有权与生命周期

Rust的所有权(ownership)和生命周期(lifetime)模型是保证Rust在没有GC的情况下依然能保障内存安全的基础.

  • Rust中每一个值都有一个所有者(owner)
  • 每个值有且只有一个所有者
  • 当所有者(owner)离开作用域的时候, 这个值会被销毁

所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。

当我们使用一个变量时, 它从声明完成到其所在的作用域结束前它都是有效的.

Rust展开
1234
{                          // 作用域开始, s 是无效的
    let s = "roadup";      // 变量 s 声明完成, s开始生效
    // use s ...
}                          // 作用域结束, s被销毁, 不再有效
  • 当变量进入作用域时生效
  • 当作用域结束无效

所有权转移

第一种情况: 赋值

但我们将一个变量赋值给另外一个变量时, 并且这个变量没有实现Copy trait, 则前一个变量的所有权将转移到后一个上, 前一个变量随即失效.

Try in playground

Rust展开
123456789

#[derive(Debug)] // #[derive(Debug, Copy, Clone)]
struct A{}

fn main(){
  let a = A{};
  let b = a; // move a to b, a is invalid
  println!("{:?}{:?}", a,b); // error
}
Rust展开
123456789101112
error[E0382]: borrow of moved value: `a`
 --> src/main.rs:8:24
  |
6 |   let a = A{};
  |       - move occurs because `a` has type `A`, which does not implement the `Copy` trait
7 |   let b = a;
  |           - value moved here
8 |   println!("{:?}{:?}", a,b);
  |                        ^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

Rust中的简单标量或它们的基本组合(各种数字类型, bool, str, char以及所有成员都实现了Copy 的元组)都实现了Copy trait. String不是基础类型, 它没有实现Copy, 但它实现了Clone, 可以直接使用String::clone 方法.

CopyClone 的关系: CloneCopy的supertrait, 实现Copy trait 的时候需要实现Clone trait.

如果实现了Clone trait 而没有实现Copy trait, 则可以使用clone方法显式复制, 但直接赋值依然会报错.

Rust展开
12
#[derive(Debug, Copy, Clone)]
struct A{}

第二种情况: 函数调用

变量的所有权遵循着相同的规律: 将值赋值给另一个变量时移动它, 当持有堆中数据的变量离开作用域时通过drop销毁它, 除非它的所有权被转移.

函数调用传参和赋值也是一样, 不过通常情况下会使用引用传递, 复制操作的消耗比较大.

Rust展开
12345678910
fn main(){
    let s = format!("hello roadup");
    print_string(s); // s are droped, it is invalid.
    
    println!("{}", s); 
}

fn print_string(s: String) {
    println!("{}", s); // here, s will be drop
} 
Rust展开
12345678910111213
error[E0382]: borrow of moved value: `s`
 --> src/main.rs:5:20
  |
2 |     let s = format!("hello roadup");
  |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3 |     print_string(s); // s are droped, it is invalid.
  |                  - value moved here
4 |     
5 |     println!("{}", s); 
  |                    ^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

使用引用可以使得变量不会转移所有权, 函数内部可以使用这份数据,但不会销毁它.

Rust展开
1234567891011121314
fn main(){
    let s = format!("hello roadup");
    print_string(&s); 
    
    println!("s is {}", s); 
}

fn print_string(s: &String) {
    println!("fn internal s = {}", s); // s are reference
} 

// output:
// fn internal s = hello roadup
// s is hello roadup

当引用作为函数参数的时候被称为 借用(brorowing), 如果尝试对借用变量进行修改则也会报错, 默认借用为不可变的, 如果希望修改借用值, 则需要使用可变借用

Rust展开
1234567891011
fn main(){
    let mut s = format!("hello ");
    print_string(&mut s); 
    
    println!("s is {}", s); 
}

fn print_string(s: &mut String) {
    s.push_str("roadup"); // it works
    println!("fn internal s = {}", s);
} 

可变借用可以赋值给不可变借用, 但反过来是不可以的.

可变性在作用域内有使用限制:

  • 同一数据的同一作用域的有效作用范围内可以变引用有且只有一个
  • 拥有可变引用时不能同时拥有不可变引用.
  • 用时拥有多个不可变引被允许的

同一作用域的有效作用范围, 即在使用可变引用之前不可变引用如果失效则这个不可变引用时被允许的, 反之这是不被允许的. Try in playground

Rust对可变引用的使用限制是为了在编译时起杜绝数据竞争, 这是Rust保障内存安全的一部分. 数据竞争会导致未定义的行为, 难以发现和跟踪, 并且难以诊断修复. 数据竞争通常有三种行为触发:

  • 多个指针同时访问同一数据
  • 至少有一个指针用来修改数据
  • 没有数据同步机制

第三种情况: 返回值

当我们在一个函数返回一个数据时, 这份数据的所有权将转移给函数的调用者

Rust展开
123456789101112
fn main(){
    let s = print_string(); 
    println!("s is {}", s); 
}

fn print_string()-> String {
    let mut s = format!("hello ");
    s.push_str("roadup");
    return s
}
// output: 
// s is hello roadup

如果我们返回的是 &String , 则会报错, 因为会造成空指针引用, 即引用所指向的数据已被释放, 这也是一个内存安全问题. Rust 将通过生命周期来解决这个问题, 他是数据有效性的保障.

- roadup -