MST

星途 面试题库

面试题:Rust内存安全机制之所有权与借用

请阐述Rust中所有权(ownership)和借用(borrowing)的概念,说明它们如何保障内存安全。并且举例说明在一个函数调用场景下,所有权是如何转移的,以及借用可能出现的错误类型及原因。
42.5万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

所有权(ownership)概念

  1. 定义:Rust 中的所有权是一种管理内存的机制。每个值在 Rust 中都有一个变量作为其所有者(owner)。当所有者超出作用域时,这个值将被释放。
  2. 内存安全保障:通过所有权系统,Rust 确保每个内存块只有一个所有者,避免了诸如悬空指针(dangling pointers)、双重释放(double free)等内存安全问题。例如,当一个变量离开作用域时,Rust 自动调用该变量的drop函数释放其占用的内存,这保证了内存的正确释放。

借用(borrowing)概念

  1. 定义:借用允许在不转移所有权的情况下使用一个值。通过使用&操作符创建一个引用,这个引用指向所有者的值,从而可以在不获取所有权的前提下访问该值。借用分为不可变借用(&T)和可变借用(&mut T)。不可变借用允许多个同时存在,但可变借用在同一时间只能有一个。
  2. 内存安全保障:借用规则确保在任何时刻,要么只有一个可变引用(可写),要么有多个不可变引用(只读),但绝不同时存在可变和不可变引用。这防止了数据竞争(data races),即多个指针同时读写数据导致未定义行为的情况。

函数调用场景下所有权转移示例

fn main() {
    let s = String::from("hello"); // s 是所有者
    take_ownership(s); // s 的所有权转移到 take_ownership 函数中
    // 这里不能再使用 s,因为所有权已转移
}

fn take_ownership(s: String) {
    println!("{}", s);
    // s 离开作用域,内存被释放
}

在上述代码中,main函数中创建的String类型变量s的所有权在调用take_ownership函数时被转移。take_ownership函数成为s的新所有者,当函数结束时,s所占用的内存被释放。

借用可能出现的错误类型及原因

  1. 悬垂引用(Dangling References):在 Rust 中通常不会出现悬垂引用,因为借用规则确保了引用指向的对象不会在引用之前被释放。例如:
fn main() {
    let reference_to_nothing;
    {
        let s = String::from("hello");
        reference_to_nothing = &s;
    }
    // 这里会报错,因为 s 已经离开作用域被释放,而 reference_to_nothing 仍然指向已释放的内存
    println!("{}", reference_to_nothing); 
}

编译器会阻止这种情况,因为s{}块结束时被释放,而reference_to_nothings释放后仍然试图引用它。

  1. 数据竞争(Data Races):数据竞争在 Rust 中通过借用规则避免。如果同时存在可变和不可变引用,或者存在多个可变引用,编译器会报错。例如:
fn main() {
    let mut s = String::from("hello");
    let r1 = &s; // 不可变借用
    let r2 = &mut s; // 这里会报错,因为已经有一个不可变借用 r1,不能同时有可变借用 r2
    println!("{}, {}", r1, r2); 
}

在这个例子中,试图在已有不可变借用r1的情况下创建可变借用r2,编译器会拒绝编译,因为这可能导致数据竞争。