Rust - 所有权
现代 Rust:理解所有权
Section titled “现代 Rust:理解所有权”在现代系统编程中,正确管理内存对于性能和安全性至关重要。Rust 采用一种独特的概念——**所有权(Ownership)来解决这个问题。要理解所有权,我们首先必须了解程序通常是如何通过栈(Stack)和堆(Heap)**来管理内存的。
栈与堆:快速回顾
Section titled “栈与堆:快速回顾”栈用于存放编译时大小已知且固定的数据。它速度极快,因为它是一个简单的“后进先出”(LIFO)结构。可以将其想象成一叠盘子:你将新盘子放在最上面,也从最上面取走盘子。所有标量类型(scalar types),如 i32、f64、bool 和 char 都存储在栈上。
堆用于存放编译时大小未知或可能改变的数据,包括像 String 或 Vec<T> 这种可以动态增长的类型。当你需要在堆上分配空间时,你会向操作系统请求,操作系统会返回一个你存储在栈上的指针(pointer)(即内存地址)。这个过程称为“分配(allocation)”,比压入栈(push to the stack)要慢。堆的组织方式不如栈有序,更像是寻找停车场里的空位。
所有权是 Rust 解决内存管理问题的方案,它替代了垃圾回收器或手动内存管理(malloc/free)的需求。所有权由编译器强制执行的一组简单规则来管理:
- 一个所有者: Rust 中的每个值都只有一个变量是其“所有者”。
- 一次一个地方: 在任何给定时间,一个值只能有一个所有者。
- 超出作用域即丢弃: 当所有者超出其作用域时,它拥有的值会自动被丢弃(即其内存被释放)。
让我们看看这些规则是如何运作的。当我们写 let s = String::from("hello"); 时,s 是堆上分配的字符串数据 "hello" 的所有者。
所有权转移:“移动”
Section titled “所有权转移:“移动””当我们将一个堆分配的值从一个变量赋值给另一个变量时,Rust 会执行一次移动(move)。这并非浅拷贝(shallow copy);所有权被转移了。
fn main() { // s1 拥有在堆上分配的 String 数据。 let s1 = String::from("hello");
// String 数据的所有权从 s1 移动到 s2。 // s1 不再有效,无法使用。 let s2 = s1;
// 这将导致编译时错误:借用了已移动的值:`s1` // println!("s1 is: {}", s1);
println!("s2 is: {}", s2); // 这是有效的。}这可以防止“二次释放(double free)”错误,即两个变量在超出作用域时尝试释放同一块内存。通过使 s1 失效,Rust 在编译时保证了内存安全。
所有权与函数
Section titled “所有权与函数”将值传递给函数也会移动所有权。同样的规则适用。
fn main() { let s = String::from("world"); // s 进入作用域
// s 的值被移动到函数中... // ... s 在这里不再有效。 takes_ownership(s);
let x = 5; // x 进入作用域
// x 是一个只在栈上的类型,因此它被复制,而不是移动。 makes_copy(x);
println!("x is still accessible: {}", x); // x 仍然可以访问:{},这没问题!} // 在这里,x 超出作用域。`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 超出作用域。没有发生特殊的事情。类似地,从函数返回一个值也会将所有权转移给接收结果的变量。
Copy Trait:只在栈上的数据
Section titled “Copy Trait:只在栈上的数据”那么为什么在将 x 赋值给函数后,我们仍然可以使用它呢?这是因为简单的标量类型实现了 Copy trait。如果一个类型实现了 Copy,那么变量会被复制而不是移动。这是可行的,因为它们的大小已知且完全存储在栈上,使得复制操作非常廉价。
- 所有整数类型,如
u32。 - 布尔类型,
bool。 - 所有浮点类型,如
f64。 - 字符类型,
char。 - 元组(Tuples),如果它们只包含也实现了
Copy的类型。
最佳实践:借用
Section titled “最佳实践:借用”不断地来回传递所有权可能会很繁琐。Rust 有一个强大的特性,称为借用(borrowing),它允许你在不取得值所有权的情况下使用它。我们使用 & 符号创建值的引用(reference)。
fn main() { let s1 = String::from("Rust is powerful!");
// 我们传递 s1 的引用,因此不会移动所有权。 let len = calculate_length(&s1);
// s1 在这里仍然有效! println!("The length of '{}' is {}.", s1, len);}
// 函数签名接受一个 String 的引用。fn calculate_length(s: &String) -> usize { s.len()} // 在这里,s 超出作用域。但因为它不拥有所有权, // 所以什么都不会发生。原始的 `s1` 不会被丢弃。所有权、移动和借用这些概念是 Rust 无需垃圾回收器即可提供内存安全性的基石,从而实现了无与伦比的性能和可靠性。