跳到主要内容

理解 Rust 的所有权概念

鱼雪

Rust 的所有权系统是编译器用来处理内存,确保程序内存使用安全性的关键方面。 与一些具有垃圾回收或显式内存分配和释放的语言不同,Rust 采用了一种独特的方法。 它通过一组规则来管理内存,这些规则由编译器检查,确保程序保持内存安全。

以下是 Rust 所有权系统的三个关键规则:

  1. Rust 中的每个值都有一个所有者。
  2. 同一时间只能有一个所有者。
  3. 当所有者超出范围时,该值将被丢弃,内存将被释放。

例如:

fn main() {
// 规则 1:Rust 中的每个值都有一个所有者
let mut s = String::from("Hello");

// 规则 2:同一时间只能有一个所有者
let s1 = s; // s1 现在拥有字符串;s 不再有效

// 规则 3:当所有者超出范围时,该值将被丢弃
// 当 s1 超出范围时,值 "Hello" 将被丢弃
}

这些规则确保 Rust 程序有效地管理内存,防止与内存错误相关的常见陷阱。 理解所有权对于编写健壮且安全的 Rust 代码至关重要。

所有权和函数

Rust 的所有权概念不仅适用于变量,还适用于将值传递给函数。 当将值传递给函数时,它要么移动,要么复制,就像在变量赋值期间一样。 让我们通过一些带有注释的示例来探讨这个问题。

fn main() {
let s = String::from("welcome"); // s 进入作用域
takes_ownership(s); // s 的值移动到函数中,此处不再有效
let x = 8; // x 进入作用域
makes_copy(x); // x 将移动到函数中,但 i32 是 Copy 类型,所以在此之后仍然可以使用 x
} // 此处,首先是 x 超出范围,然后是 s。因为 s 的值已被移动,所以不会发生特殊的事情。

fn takes_ownership(s1: String) {
// s1 进入作用域
println!("{}", s1);
} // 此处,s1 超出范围,其内存被释放。

fn makes_copy(y: i32) {
// y 进入作用域
println!("{}", y);
} // 此处,y 超出范围。不会发生特殊的事情。

如果我们尝试在调用 takes_ownership 后使用 s, Rust 将引发编译时错误,确保我们的代码正确性。

返回值和作用域

函数还可以通过返回值传递所有权。考虑以下带有注释的示例:

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 s4 = String::from("welcome"); // s4 进入作用域
s4 // 返回 s4 并移动到调用它的函数中
}

fn takes_and_gives_back(s5: String) -> String {
// s5 进入作用域
s5 // 返回 s5 并移动到调用它的函数中
}

所有权模式保持一致:将值分配给另一个变量将其移动。 如果具有堆数据的变量超出范围,除非所有权已被转移,否则该值将被清理。

在 Rust 中的借用和引用

在 Rust 中,借用和引用是使多个变量能够与和操作数据而不获取所有权的基本概念,提供对程序中内存使用的更强大的控制。

借用

借用是一种在不声称所有权的情况下临时使用值的行为。 这允许多个变量读取和访问数据而不更改它。 借用通过引用实现,引用是对数据的不可变指针。 让我们通过一个简化的例子来了解这个概念:

fn main() {
let s = String::from("hello");
let len = s.len();
println!("String length: {}", len);
let mut s1 = s;
// s1[0] = 'h'; // 取消注释此行会导致错误,因为 s1 现在拥有数据
println!("String after modifying: {}", s1);
}

在这个例子中,String 值 slen 函数和变量 s1 借用。 但是,只有 len 函数以不可变方式借用该值,而 s1 以可变方式借用该值。 这意味着 s1 可以修改 s 的值,但 len 不能。

引用

在 Rust 中,引用是对数据的不可变指针。它允许您访问和读取数据而不进行修改。 由 & 符号表示的引用必须遵循特定的借用规则:

  1. 引用的作用域不能超过所有者的作用域。
  2. 不能有对同一值的多个可变引用。

以下是演示 Rust 中引用的简化示例:

fn main() {
let s = String::from("hello");
let len = s.len();
println!("String length: {}", len);
let s1 = &s;
let len1 = s1.len();
println!("String length through reference: {}", len1);
}

在这个例子中,len 函数采用对 String 值 s 的不可变引用。 类似地,变量 len1 采用对 s 的不可变引用。 这允许多个变量访问 s 的值而不进行修改。

总之,在 Rust 中,借用和引用使多个变量能够与和操作数据而不声称所有权, 为程序中内存使用提供了更大的灵活性和控制。

参考

Rust 所有权的官方文档