所有权

所有权的规则:

  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者。
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者。
  3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)。

转移所有权

let x = 5;
let y = x;

这段代码并没有发生所有权的转移。代码首先将 5 绑定到变量 x,接着拷贝 x 的值赋给 y,最终 xy 都等于 5,因为整数是 Rust 基本数据类型,是固定大小的简单值,因此这两个值都是通过自动拷贝的方式来赋值的,都被存在栈中,完全无需在堆上分配内存。

整个过程中的赋值都是通过值拷贝的方式完成(发生在栈中),因此并不需要所有权转移

let s1 = String::from("hello");
let s2 = s1;

String 类型是一个复杂类型,由存储在栈中的堆指针字符串长度字符串容量共同组成。

s1 被赋予 s2 后,Rust 认为 s1 不再有效,因此也无需在 s1 离开作用域后 drop 任何东西,这就是把所有权从 s1 转移给了 s2s1 在被赋予 s2 后就马上失效了。

let x: &str = "hello, world";
let y = x;
println!("{},{}",x,y);

在这个例子中,x 只是引用了存储在二进制可执行文件( binary )中的字符串 "hello, world",并没有持有所有权。

let y = x 中,仅仅是对该引用进行了拷贝,此时 yx 都引用了同一个字符串。

克隆(深拷贝)

Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何自动的复制都不是深拷贝,可以被认为对运行时性能影响较小。

如果我们确实需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的方法。

let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);

拷贝(浅拷贝)

浅拷贝只发生在栈上,因此性能很高

let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);

这段代码不会报所有权的错误。因为基本类型在编译时是已知大小的,会被存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y 后使 x 无效(xy 都仍然有效)。换句话说,这里没有深浅拷贝的区别,因此这里调用 clone 并不会与通常的浅拷贝有什么不同,我们可以不用管它(可以理解成在栈上做了深拷贝)。

Copy

Rust 有一个叫做 Copy 的特征,可以用在类似整型这样在栈中存储的类型。如果一个类型拥有 Copy 特征,一个旧的变量在被赋值给其他变量后仍然可用,也就是赋值的过程即是拷贝的过程。

==任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy 的==。

函数传值与返回

将值传递给函数,一样会发生 移动 或者 复制,就跟 let 语句一样。

fn main() {
    let s = String::from("hello");  // s 进入作用域
    takes_ownership(s);             // s 的值移动到函数里
                                    // s 到这里不再有效
    let x = 5;                      // x 进入作用域
    makes_copy(x);                  // x 应该移动函数里,
                                    // 但 i32 是 Copy 的,所以在后面可继续使用 x
} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,所以不会有特殊操作
 
fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
 
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
    println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

函数返回值也有所有权。

fn main() {
    let s1 = gives_ownership();         // gives_ownership 将返回值移给 s1
    let s2 = String::from("hello");     // s2 进入作用域
    let s3 = takes_and_gives_back(s2);  // s2 被移动到 takes_and_gives_back 中,它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,所以什么也不会发生。s1 移出作用域并被丢弃。
 
fn gives_ownership() -> String {             // gives_ownership 将返回值移动给调用它的函数
    let some_string = String::from("hello"); // some_string 进入作用域.
    some_string                              // 返回 some_string 并移出给调用的函数
}
 
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
    a_string  // 返回 a_string 并移出给调用的函数
}

借用

如果仅仅支持通过转移所有权的方式获取一个值,那会让程序变得复杂。Rust 通过借用(Borrowing)这个概念来解决这个问题,获取变量的引用,称之为借用(borrowing)

引用和解引用

常规引用是一个指针类型,指向了对象存储的内存地址。

fn main() {
    let x = 5;
    let y = &x;// 创建一个 i32 值的引用 y
 
    assert_eq!(5, x);
    assert_eq!(5, *y);// 使用解引用运算符来解出 y 所使用的值
}

变量 x 存放了一个 i325yx 的一个引用。可以断言 x 等于 5。然而,如果希望对 y 的值做出断言,必须使用 *y 来解出引用所指向的值(也就是解引用)。一旦解引用了 y,就可以访问 y 所指向的整型值并可以与 5 做比较。

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);// 用 s1 的引用作为参数传递给 calculate_length 函数,而不是所有权
    println!("The length of '{}' is {}.", s1, len);// 还可以继续使用 s1
}
 
fn calculate_length(s: &String) -> usize {// s 是对 String 的引用
    s.len()
}// 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,所以什么也不会发生

通过 &s1 语法,我们创建了一个指向 s1 的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。同理,函数 calculate_length 使用 & 来表明参数 s 的类型是一个引用。

这里,& 符号即是引用,它们允许你使用值,但是不获取所有权,如图所示:

不可变引用和可变引用

fn main() {
    let s = String::from("hello");
    change(&s);
}
 
fn change(some_string: &String) {
    some_string.push_str(", world");// 报错
}

正如变量默认不可变一样,引用指向的值默认也是不可变的。

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}
 
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

声明 s 是可变类型,其次创建一个可变的引用 &mut s 和接受可变引用参数 some_string: &mut String 的函数。

可变引用同时只能存在一个,即同一作用域,特定数据只能有一个可变引用

let mut s = String::from("hello");
 
let r1 = &mut s; // first mutable borrow occurs here
let r2 = &mut s; // second mutable borrow occurs here
 
println!("{}, {}", r1, r2); // first borrow later used here

出错的原因在于,第一个可变借用 r1 必须要持续到最后一次使用的位置 println!,在 r1 创建和最后一次使用之间,我们又尝试创建第二个可变借用 r2

可变引用与不可变引用不能同时存在

let mut s = String::from("hello");
 
let r1 = &s; // immutable borrow occurs here
let r2 = &muts; // mutable borrow occurs here
 
println!("{}, {}, and {}", r1, r2);// immutable borrow later used here

引用的作用域

引用的作用域 s 从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号 }

Non-Lexical Lifetimes(NLL)

专门用于找到某个引用在作用域(})结束前就不再被使用的代码位置。

悬垂引用(Dangling References)

悬垂引用也叫做悬垂指针,意思为指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其它变量重新使用。

在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你获取数据的引用后,编译器可以确保数据不会在引用结束前被释放,要想释放数据,必须先停止其引用的使用。

fn main() {
    let reference_to_nothing = dangle();
}
 
fn dangle() -> &String { // dangle 返回一个字符串的引用
    let s = String::from("hello");
    &s // 返回字符串 s 的引用
}// 这里 s 离开作用域并被丢弃。其内存被释放。

s 是在 dangle 函数内创建的,当 dangle 的代码执行完毕后,s 将被释放,但是此时我们又尝试去返回它的引用。这意味着这个引用会指向一个无效的 String

其中一个很好的解决方法是直接返回 String

fn no_dangle() -> String {
    let s = String::from("hello");
    s
}