面试题答案
一键面试所有权(ownership)概念
- 定义:Rust 中的所有权是一种管理内存的机制。每个值在 Rust 中都有一个变量作为其所有者(owner)。当所有者超出作用域时,这个值将被释放。
- 内存安全保障:通过所有权系统,Rust 确保每个内存块只有一个所有者,避免了诸如悬空指针(dangling pointers)、双重释放(double free)等内存安全问题。例如,当一个变量离开作用域时,Rust 自动调用该变量的
drop
函数释放其占用的内存,这保证了内存的正确释放。
借用(borrowing)概念
- 定义:借用允许在不转移所有权的情况下使用一个值。通过使用
&
操作符创建一个引用,这个引用指向所有者的值,从而可以在不获取所有权的前提下访问该值。借用分为不可变借用(&T
)和可变借用(&mut T
)。不可变借用允许多个同时存在,但可变借用在同一时间只能有一个。 - 内存安全保障:借用规则确保在任何时刻,要么只有一个可变引用(可写),要么有多个不可变引用(只读),但绝不同时存在可变和不可变引用。这防止了数据竞争(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
所占用的内存被释放。
借用可能出现的错误类型及原因
- 悬垂引用(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_nothing
在s
释放后仍然试图引用它。
- 数据竞争(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
,编译器会拒绝编译,因为这可能导致数据竞争。