跳到主要内容

Rust所有权机制

提示
  1. 作用域和内存管理:Rust 的所有权机制通过变量作用域管理内存,确保内存安全和无内存泄漏。变量只在其定义的代码块(作用域)内有效,离开作用域时内存被释放。
  2. 所有权规则和数据移动:Rust 的核心所有权规则包括:每个值有一个所有者,同一时间只能有一个所有者,所有者离开作用域时值被丢弃。数据移动意味着从一个变量转移值的所有权到另一个变量。
  3. 数据复制和函数中的所有权:基本类型(如整数)遵循 Copy 特质,可快速复制而不受所有权规则影响。在函数调用中,传递变量可能导致所有权移动或数据复制,取决于数据类型(堆上的类型移动所有权,栈上的类型复制数据)。

Rust 包含了一个所有权机制来管理程序的内存。所有权是一组确保 Rust 程序内存安全的规则。

Rust 中的所有权特性允许我们的程序运行时无内存泄漏和缓慢。

Rust 中的变量作用域

作用域是程序中一个代码块,在这个代码块内变量是有效的。变量的作用域定义了其所有权。

例如,

// `name` 在这里无效且无法使用,因为它尚未声明
{ // 代码块开始
let name = String::from("Ram Nepali"); // 从这点开始,`name` 是有效的

// 使用 `name` 做一些事情
} // 代码块结束
// 这个作用域结束,`name` 不再有效且无法使用

在这里,变量 name 仅在代码块内部有效,即在大括号 {} 之间。我们不能在闭合大括号之外使用 name 变量。

每当一个变量离开作用域时,它的内存会被释放。

想了解更多关于 Rust 中变量作用域的信息,请访问 Rust 变量作用域

Rust 中的所有权规则

Rust 有一些所有权规则。在我们通过一些示例学习时,请牢记这些规则:

  1. Rust 中的每个值都有一个所有者。
  2. 任何时候只能有一个所有者。
  3. 当所有者离开作用域时,该值将被丢弃。

Rust 中的数据移动

有时,我们可能不希望变量在作用域结束时被丢弃。相反,我们希望将一个项目的所有权从一个绑定(变量)转移到另一个。

这里有一个例子来理解 Rust 中的数据移动和所有权规则。

fn main() {
// String 值的所有者
// 规则 1
let fruit1 = String::from("Banana");

// 所有权移动到另一个变量
// 任何时候只有一个所有者
// 规则 2
let fruit2 = fruit1;

// 不能打印 fruit1,因为所有权已经移动
// 错误,超出作用域,值被丢弃
// 规则 3
// println!("fruit1 = {}", fruit1);

// 在屏幕上打印 fruit2 的值
println!("fruit2 = {}", fruit2);
}

输出

fruit2 = Banana

让我们详细看看这个示例,特别是这两行代码:

let fruit1 = String::from("Banana");
let fruit2 = fruit1;

在这里,fruit1String 的所有者。

String 在栈和堆上存储数据。这意味着当我们将 String 绑定到变量 fruit1 时,内存表示如下图所示:

String 值的内存表示

String 在栈中保存指向保存字符串内容的内存的指针、长度和容量。右侧的堆存储 String 的内容。

现在,当我们将 fruit1 分配给 fruit2 时,内存表示如下图所示:

String 值的内存表示移动

Rust 将使第一个变量 fruit1 无效(丢弃),并将值移动到另一个变量 fruit2。这样两个变量就不能指向同一内容。在任何时候,只有一个所有者拥有该值。

注意: 上述概念适用于在内

Rust 中的数据复制

像整数、浮点数和布尔类型这样的基本类型不遵循所有权规则。这些类型在编译时大小已知,完全存储在栈上,因此可以快速制作实际值的副本。例如,

fn main() {
let x = 11;

// 将 x 的数据复制到 y
// 这里不适用所有权规则
let y = x;

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

输出

x = 11, y = 11

这里,即使 y 被赋值给 xx 变量之后仍然可以使用,不需要担心所有权问题,与移动不同。

这种复制是由于 Rust 中基本类型可用的 Copy 特质。当我们将 x 赋值给 y 时,数据的副本被制作。

特质(trait)是 Rust 中定义共享行为的一种方式。要了解更多关于特质的信息,请访问 Rust 特质

函数中的所有权

将变量传递给函数将进行移动或复制,就像赋值一样。只在栈上的类型将在传递到函数时复制数据。堆数据类型将把变量的所有权移动到函数。

让我们看一些示例。

1. 将字符串传递给函数

fn main() {
let fruit = String::from("Apple"); // fruit 进入作用域

// fruit 的所有权移动到函数中
print_fruit(fruit);

// fruit 已经移动到函数中,所以在这里不再可用
// 错误
// println!("fruit = {}", fruit);
}

fn print_fruit(str: String) { // str 进入作用域
println!("str = {}", str);
} // str 离开作用域并被释放,内存也被释放

输出

str = Apple

这里,fruit 变量的值被移动到 print_fruit() 函数中,因为 String 类型使用堆内存。

2. 将整数传递给函数

fn main() {
// number 进入作用域
let number = 10;

// number 的值被复制到函数中
print_number(number);

// number 变量在这里可以使用
println!("number = {}", number);
}

fn print_number(value: i32) { // value 进入作用域
println!("value = {}", value);
} // value 离开作用域

输出

value = 10
number = 10

这里,number 变量的值被复制到 print_number() 函数中,因为 i32(整数)类型使用栈内存。