向量 vector 的 "所有权"
如下代码,第25行会在编译阶段报错:
rust
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
let mut vec = vec;
vec.push(88);
vec
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
// TODO: Make both vectors `vec0` and `vec1` accessible at the same time to
// fix the compiler error in the test.
#[test]
fn move_semantics2() {
let vec0 = vec![22, 44, 66];
let vec1 = fill_vec(vec0);
assert_eq!(vec0, [22, 44, 66]);
assert_eq!(vec1, [22, 44, 66, 88]);
}
}
原因是:在 Rust 中,Vec 是一个拥有堆上数据的类型。
当你把它当作参数传给 fill_vec(vec0) 时,所有权从 vec0 转移到函数的形参里:
fn fill_vec(vec: Vec<i32>) -> Vec<i32> { ... }
let vec1 = fill_vec(vec0); // vec0 的所有权被 move 给函数形参了
// ^^^^^^^^ ^^^^^^^
// ↑ ↑
// 变量 函数参数
移动发生后,原来的变量 vec0 不再拥有数据,所以编译器禁止再使用它;
这正是为了避免两个 Vec 同时尝试释放同一块内存(双重释放)的不安全情况。
因此下面的 assert_eq!(vec0, ...) 会报错------vec0 已经不再有效。
常见解决办法:
- 传 vec0.clone() 给函数,保留一个副本;
- 或者让函数借用:fn fill_vec(vec: &mut Vec) { ... }。
这两种方式都避免了移动,从而让 vec0 在后续仍可使用。(我没试过)
如25行改为:
rust
let vec1 = fill_vec(vec0.clone());
不可变矢量通过函数形参转可变矢量
给 fill_vec 的形参开头加上 mut 关键字
rust
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
vec.push(88);
vec
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn move_semantics3() {
let vec0 = vec![22, 44, 66];
let vec1 = fill_vec(vec0);
// vec0 此时不能再用,所有权已经交给 vec1
assert_eq!(vec1, [22, 44, 66, 88]);
}
}
矢量的别名
每个变量只能拥有一个别名,如果要用其它别名取代,需要确保程序后续没有再使用原来的别名(更好的理解:所有权同一时间只能给一个变量):
rust
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
// TODO: Fix the compiler errors only by reordering the lines in the test.
// Don't add, change or remove any line.
#[test]
fn move_semantics4() {
let mut x = Vec::new();
let y = &mut x;
// `y` used here.
y.push(42);
// The mutable reference `y` is not used anymore,
// therefore a new reference can be created.
let z = &mut x;
z.push(13);
assert_eq!(x, [42, 13]);
}
}
函数获取变量所有权
通常来说,如果一个函数对某个变量是只读的,就不需要获取该变量数据的所有权。
若这个函数需要改变某个变量,那么需要获取该变量数据的所有权。(但听说也有 borrow mutably 方案,没试过)
rust
#![allow(clippy::ptr_arg)]
// Borrows instead of taking ownership.
// It is recommended to use `&str` instead of `&String` here. But this is
// enough for now because we didn't handle strings yet.
fn get_char(data: &String) -> char {
data.chars().last().unwrap()
}
// Takes ownership instead of borrowing.
fn string_uppercase(mut data: String) {
data = data.to_uppercase();
println!("{data}");
}
fn main() {
let data = "Rust is great!".to_string();
get_char(&data);
string_uppercase(data);
}
可变借用 vs 取得所有权
&mut T 和 mut T 在 Rust 里代表两种不同的 "使用权利":
-
🔧 可变借用(&mut T)
是什么:一个可变的引用,函数只借用数据,自己不拥有。
特点:
- 只能在借用期间访问,出了作用域就没了。
- 借用期间,调用方不能同时再使用这个值(编译器保证独占访问)。
- 函数结束后,原值还在,调用方继续拥有它。
适用场景:你想 修改调用者的数据,但不想拿走它,比如往 Vec 里 push。
-
🔄 取得所有权(mut T)
是什么:把数据的所有权从调用方移动到函数里,函数现在完全控制它。
特点:
- 函数可自由变更、移动甚至丢弃这个值。
- 调用方在调用之后就不能再使用这个变量了(除非函数把它返回)。
- 没有借用期限制,值属于函数直到它被返回或移动到别处。
适用场景:你要 消费这个值、转交给其他地方或返回一个新值给调用者。
📝 总结
&mut T 是一次 临时的独占借用,原主人保留所有权。
mut T 是一次 所有权转移,函数获得永久控制权。
选择哪一个,取决于调用方之后是否还需要使用这个值。