👈 1 基础

存储结构——栈与堆:

  • 堆:在内存中,存放大小不固定的数据。
  • 栈:在高速缓存中,存放大小固定的数据(包括指向堆数据的指针)。

所有权原则:

  • 每一个值被一个变量所拥有,变量称为值的所有者。
  • 每一个值在同一时间只能被一个变量所拥有。
  • 当所有者(变量)离开作用域范围时,属于它的值将被丢弃。

&str:不可变的字符串字面量。

let s1: &str = "Rust";

String:动态的、可变的字符串。

let s2: String = String::from("Rust");
 
let mut s = String::from("Rust");
s.push_str(", world!"); // 向 String 追加 &str

String 由 3 部分组成:指向存放字符串内容内存的指针,长度,容量。这 3 部分都存储在栈上,而其具体内容存储在堆上。

现在,执行 s1 = s2,此时 Rust 有 3 种选择:

  • 浅拷贝:将 s1 的栈上信息拷贝给 s2

如果这样做,当 s1s2 离开作用域时,它们尝试释放同一块内存,属于 二次释放(double free)错误,是一个内存安全性 bug。

  • 深拷贝:同时拷贝 s1 的栈、堆信息给 s2

在堆上数据比较大时,会对运行时性能造成很大影响。

  • 移动(move):执行 s2 = s1 后,使 s1 失效。这也是 Rust 实际采用的方式。

拷贝:

整数为基本数据类型,大小固定,存储在栈内,通过自动拷贝(浅拷贝)的方式赋值。

let x = 5; // 将值 5 绑定到变量 x
let y = x; // 将 x 的值拷贝, 赋给变量 y
// x 仍有效

String 为复杂类型,由栈内指针、字符串长度(已用大小)、字符串容量(分配大小)共同组成,大小不固定,存储在堆内,无法自动拷贝。

let x = String::from("Rust"); // 将 String::from 创建的值赋给变量 x
let y = x; // 所有权转移
// x 已失效

执行 let y = x 后,x 不再有效,对其值的所有权转移给 y

let x: &str = "Rust"; // 变量 x 引用字符串字面量
let y = x; // x 的引用值浅拷贝赋给 y
// x 仍有效

前一段中,x 持有了通过 String::from 调用创建的 String 值的所有权,而此处,x 只是引用了字符串字面量 "Rust",并不对该值持有所有权。

clone():创建数据的深拷贝,将大幅影响性能。

let x = String::from("Rust"); // 将 String::from 创建的值赋给变量 x
let y = x.clone(); // 将 x 的值深拷贝赋给 y
// x 仍有效

Copy 特征:拥有 Copy 特征的类型变量在被赋值给其他变量后不会失效。

具有 Copy 特征的类型:

  • 基本类型的组合。
  • 无需分配内存的或某种形式资源的类型。

例如,所有整数类型,所有浮点数类型,布尔类型,字符类型,元组,不可变引用 &T

作用域:

fn main() {
	let s = String::from("Rust"); // s 的作用域开始
	takes_ownership(s); // s 移动到方法内
	// s 已失效
	
	let a = 5; // a 的作用域开始
	makes_copy(a); // a 移动到方法内
	// a 仍有效
}
 
fn takes_ownership(x: String) {
	println!("{x}");
} // x 移出作用域, 调用 drop(), 内存释放
 
fn makes_copy(x: i32) {
	println!("{x}");
} // x 移出作用域, 无其他操作

要在调用 takes_ownership() 后继续使用 s,只需改为传入 s.clone()

fn main() {
	let s = String::from("Rust");
	takes_ownership(s.clone()); // 改为传入 s.clone()
	// s 仍有效
}

更多示例:

fn main() {
	let s1 = gives_ownership(); // s1 的作用域开始
	let s2 = String::from("Rust"); // s2 的作用域开始
	let s3 = takes_and_gives_back(s2); // s2 移动到方法内, 方法的返回值转移给 s3
	// s1 仍有效, s2 已失效, s3 仍有效
}
 
// 将返回值转移给调用者
fn gives_ownership() -> String {
	let x = String::from("Hello");
	x
}
 
// 接收一个值, 接着转移回调用者
fn takes_and_gives_back(x: String) -> String {
	x
}

引用:

fn main() {
	let x = 5; // x 的类型为 i32
	let y = &x; // y 的类型为 &i32, 对 i32 的引用
	assert_eq!(5, x); // success
	assert_eq!(5, *y); // 解引用 y, 得到 i32 值 5 // success
}

传入引用不会转移所有权:

fn main() {
    let s = String::from("Rust");
    let n = get_len(&s);
    // s 仍有效
}
 
fn get_len(s: &String) -> usize {
    s.len()
}

引用指向的值默认不可变:

fn main() {
	let s = String::from("Hello");
	change(&s);
}
 
fn change(s: &String) {
	s.push_str(", Rust!"); // error
}

改为可变引用则可修改:

fn main() {
	let mut s = String::from("Hello"); // 改为可变
	change(&mut s); // 改为传入可变引用
}
 
fn change(s: &mut String) { // 改为接收可变引用
	s.push_str(", Rust!"); // ok
}

引用的作用域的结束位置:最后一次使用的行。

在同一作用域,一个变量在同一时间只能有一个可变引用:

// not ok
fn main() {
	let mut s = String::from("Rust");
	let r1 = &mut s;
	let r2 = &mut s; // error: 无法同时对 s 进行 2 次可变借用
	println!("{r1}, {r2}"); // r1 的最后一次使用
}
// ok
fn main() {
    let mut s = String::from("Rust");
    let r1 = &mut s;
    print!("{r1}"); // r1 的最后一次使用, r1 的作用域在此结束
    let r2 = &mut s; // 之后, 可以创建 r2
    print!("{r2}");
}
// not ok
fn main() {
    let mut s = String::from("Rust");
    let r1 = &mut s;
    print!("{}", r1);
    let r2 = &mut s;
    print!("{r2}");
    print!("{r1}"); // 加上此行后出错, 因为它延长了 r1 的作用域, 导致 s2 和 s1 同时存在
}
// ok
fn main() {
    let mut s = String::from("Rust");
    let _r1 = &mut s; // r1 的作用域在此结束
    let r2 = &mut s;
    print!("{r2}");
}

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

fn main() {
    let mut s = String::from("Rust");
    let r1 = &s; // ok
    let r2 = &s; // ok
    let r3 = &mut s; // error
}

悬垂引用(dangling references):引用未释放,但其引用的变量已经释放。

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

悬垂指针和野指针的区别:

  • 悬垂指针:曾经有效,现在失效。
  • 野指针:从未有效过。

引用的总规则:

  • 在任意给定时间,要么 只能有 一个 可变引用,要么 只能有多个 不可变 引用。
  • 引用必须总是有效的。

练习:

fn main() {
    // 使用尽可能多的方法过编
    let x = String::from("Hello, Rust!");
    let y = x;
    println!("{x}, {y}");
}

答案:

fn main() {
    let x = String::from("Hello, Rust!");
    let y = x.clone(); // clone()
    println!("{x}, {y}");
}
fn main() {
    let x = "Hello, Rust!"; // 改为字面量
    let y = x;
    println!("{x}, {y}");
}
fn main() {
    let x = &String::from("Hello, Rust!"); // 改为引用
    let y = x;
    println!("{x}, {y}");
}
fn main() {
    let x = String::from("Hello, Rust!");
    let y = x.as_str(); // 创建新变量
    println!("{x}, {y}");
}
// 将 clone() 改为 copy
fn main() {
    let x = (1, 2, (), "Rust".to_string());
    let y = x.clone();
    println!("{x:?}, {y:?}");
}

答案:

fn main() {
    let x = (1, 2, (), "Rust"); // 改为字符串字面量
    let y = x;
    println!("{x:?}, {y:?}");
}

👉 3 复合类型