面试题答案
一键面试struct Data {
value: Vec<i32>
}
fn process_data(mut data: Data) -> Data {
// 对结构体进行一些操作,例如修改value
data.value.push(42);
data
}
移动语义解释
- 函数调用前:
- 当定义
Data
结构体实例时,例如let my_data = Data { value: vec![1, 2, 3] };
,my_data
拥有value
这个Vec<i32>
的所有权。 Vec<i32>
是一个堆分配类型,它在栈上有一个胖指针(包含指向堆内存的指针、长度和容量)。
- 当定义
- 函数调用时:
- 当调用
process_data(my_data)
时,my_data
的所有权被移动到process_data
函数的参数data
中。这意味着my_data
不再有效,不能再被使用。在 Rust 中,移动操作通常是廉价的,因为它只是转移了所有权,而不是复制数据。
- 当调用
- 函数返回后:
- 函数
process_data
返回修改后的data
。调用者接收返回值并获得其所有权。例如let new_data = process_data(my_data);
,new_data
现在拥有修改后的Vec<i32>
的所有权。
- 函数
避免移动的方法
- 使用引用:
- 可以将函数参数改为引用,这样所有权不会被移动。
struct Data { value: Vec<i32> } fn process_data_ref(data: &mut Data) { data.value.push(42); }
- 调用方式:
let mut my_data = Data { value: vec![1, 2, 3] }; process_data_ref(&mut my_data);
- 这里
process_data_ref
函数接收Data
结构体的可变引用,函数内部可以修改Data
的内容,但所有权仍然在调用者手中。
- 克隆数据:
- 如果
Data
结构体实现了Clone
特质,可以在函数调用前克隆数据,这样原始数据的所有权不变。
struct Data { value: Vec<i32> } impl Clone for Data { fn clone(&self) -> Data { Data { value: self.value.clone() } } } fn process_data_clone(data: Data) -> Data { data.value.push(42); data }
- 调用方式:
let my_data = Data { value: vec![1, 2, 3] }; let new_data = process_data_clone(my_data.clone());
- 这种方式会产生额外的开销,因为
clone
方法会复制Data
结构体及其内部的Vec<i32>
数据。
- 如果