Rust 所有权

所有权是一套规则,可确保 Rust 程序中的内存安全。

Rust 使用所有权模型进行内存管理,而不是垃圾回收器和手动内存管理。

这种所有权使 Rust 有别于其他语言,并允许程序在没有内存泄漏和缓慢运行的情况下运行。


Rust 中的变量作用域

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

例如,

// `name` is invalid and cannot be used here because it's not yet declared
{ // code block starts here
    let name = String::from("Ram Nepali");   // `name` is valid from this point forward
    
    // do stuff with `name`
} // code block ends
// this scope ends, `name` is no longer valid and cannot be used

在这里,变量 name 仅在代码块内可用,即在花括号 {} 之间。我们不能在结束花括号外使用 name 变量。

每当变量离开作用域时,其内存就会被释放。

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


Rust 的所有权规则

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

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

Rust 中的数据移动

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

这是一个了解 Rust 中数据移动和所有权规则的示例。

fn main() {
    // owner of the String value
    // rule no. 1 
    let fruit1 = String::from("Banana");
    
    // ownership moves to another variable
    // only one owner at a time
    // rule no. 2
    let fruit2 = fruit1;
    
    // cannot print variable fruit1 because ownership has moved
    // error, out of scope, value is dropped
    // rule no. 3
    // println!("fruit1 = {}", fruit1);
    
    // print value of fruit2 on the screen
    println!("fruit2 = {}", fruit2);
}

输出

fruit2 = Banana

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

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

在这里,fruit1String 的所有者。

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

Memory representation of a String holding the value
绑定到 fruit1 的 String (值为 "Banana") 的内存表示

String 在栈上保存一个指向存储字符串内容的内存的指针、长度和容量。图表右侧的堆存储了 String 的内容。

现在,当我们把 fruit1 赋值给 fruit2 时,内存表示如下所示

Memory representation when String value
当 String 值 "Banana" 从 fruit1 移动到 fruit2 时的内存表示

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

注意: 上述概念适用于在内存中没有固定大小且使用堆内存存储内容的数据类型。

使用 clone() 创建副本而不是移动

如果你想创建副本而不是移动,可以使用 clone() 方法。例如,

fn main() {
    // create a new String.
    let fruit1 = String::from("Banana");
    
    // create a copy of fruit1 using the clone method.
    let fruit2 = fruit1.clone();
    

    println!("fruit1 = {}", fruit1);
    
    // print value of fruit2 on the screen
    println!("fruit2 = {}", fruit2);
}

输出

fruit1 = Banana
fruit2 = Banana

注意: 使用 clone() 可能会产生额外的运行时成本,因此应明智地使用。


Rust 中的数据复制

原始类型(整数、浮点数和布尔值)在编译时的大小是已知的,并且完全存储在栈上。因此,复制原始类型成本很低,它们实现了 copy trait 而不是 move。

让我们看一个例子。

fn main() {
    let x = 11;
    
    // copies data from x to y 
    let y = x;

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

输出

x = 11, y = 11

在这里,x 被复制而不是移动,因为像整数、浮点数这样的原始类型默认实现了 Copy trait,因此会被复制。

在这里,x 变量稍后仍可使用,因为 x 被复制而不是移动,即使 y 被赋值给 x

trait 是在 Rust 中定义共享行为的一种方式。要了解有关 trait 的更多信息,请访问 Rust Trait


函数中的所有权

将变量传递给函数会发生移动或复制,就像赋值一样。仅栈类型在传递到函数时会复制数据。堆数据类型会将变量的所有权移动到函数。

让我们看一些例子。

1. 将 String 传递给函数

fn main() {
    let fruit = String::from("Apple");  // fruit comes into scope
    
    // ownership of fruit moves into the function
    print_fruit(fruit);
    
    // fruit is moved to the function so is no longer available here
    // error
    // println!("fruit = {}", fruit);
}

fn print_fruit(str: String) {   // str comes into scope
    println!("str = {}", str);
}   // str goes out of scope and is dropped, plus memory is freed

输出

str = Apple

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

2. 将整数传递给函数

fn main() {
    // number comes into scope
    let number = 10;
    
    // value of the number is copied into the function
    print_number(number);
    
    // number variable can be used here
    println!("number = {}", number);
}

fn print_number(value: i32) { // value comes into scope
    println!("value = {}", value);
}   // value goes out of scope

输出

value = 10
number = 10

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

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战