面试题答案
一键面试借用检查器工作原理
- 作用:Rust的借用检查器在编译时确保内存安全,防止悬空指针、数据竞争等问题。
- 规则:
- 一条可变借用规则:同一时间内,只能有一个可变借用(
&mut
)指向特定数据。这防止了多个可变引用同时修改数据导致的数据竞争。 - 多条不可变借用规则:可以有多个不可变借用(
&
)指向特定数据,但不可变借用和可变借用不能同时存在。这确保在数据被借用读取时不会被意外修改。
- 一条可变借用规则:同一时间内,只能有一个可变借用(
- 原生类型场景:
- 函数参数传递:当原生类型作为函数参数传递时,借用检查器会根据参数的借用类型(
&
或&mut
)进行检查。例如:
- 函数参数传递:当原生类型作为函数参数传递时,借用检查器会根据参数的借用类型(
fn print_num(num: &i32) {
println!("The number is: {}", num);
}
fn main() {
let x = 5;
print_num(&x);
}
这里x
作为不可变借用传递给print_num
函数,符合借用规则。
- 数据结构内部操作:在结构体等数据结构内部,如果成员是原生类型,借用检查器同样适用规则。例如:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn get_x(&self) -> i32 {
self.x
}
fn set_x(&mut self, new_x: i32) {
self.x = new_x;
}
}
get_x
方法获取不可变借用,set_x
方法获取可变借用,保证了内存安全。
利用或规避以实现更好性能
- 利用:
- 避免不必要的复制:对于较大的原生类型集合,如
Vec<i32>
,通过借用可以避免在函数调用时进行深拷贝。例如:
- 避免不必要的复制:对于较大的原生类型集合,如
fn sum_vec(vec: &Vec<i32>) -> i32 {
vec.iter().sum()
}
fn main() {
let my_vec = vec![1, 2, 3, 4, 5];
let result = sum_vec(&my_vec);
println!("Sum is: {}", result);
}
这里通过传递&Vec<i32>
,避免了my_vec
的复制,提高了性能。
2. 规避:
- 使用Copy
语义:对于实现了Copy
trait的原生类型(如i32
、u8
等),可以直接传递值而不是借用。例如:
fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let x = 3;
let y = 5;
let result = add_numbers(x, y);
println!("Result is: {}", result);
}
因为i32
实现了Copy
,直接传递值在性能上与借用差别不大,但代码更简洁,且避免了借用检查器的一些复杂规则。
- 使用unsafe
块:在某些极端情况下,当确定代码不会违反内存安全时,可以使用unsafe
块绕过借用检查器。但这是非常危险的,应谨慎使用。例如:
fn main() {
let mut num = 5;
let num_ref = &mut num;
let raw_ptr = num_ref as *mut i32;
unsafe {
*raw_ptr = 10;
}
println!("The number is: {}", num);
}
此代码使用原始指针绕过了借用检查器直接修改值,需确保不会导致内存安全问题。