简介
Rust 使用所有权决定程序的哪一部分负责管理某个值,以及何时可以清理该值。可以把所有权理解为责任和使用权限,而不是永久附着在某个变量名上的标签:这份责任可以移动到新的绑定上。这样无需垃圾回收器,也能避免多种内存错误。
在本实验中,你将先观察小整数如何被复制。然后,你会有意让 Rust 在一个已被移动的 String 上继续操作,阅读编译器生成的诊断信息,并使用函数返回的所有权修复数据流。代码框架已经准备好,因此每次只需进行少量、明确的修改。
观察作用域和被复制的整数
在本步骤中,你会将一个小整数复制到内部作用域,并确认原始绑定仍然可以使用。
进入准备好的项目并打开源文件:
cd /home/labex/project/ownership-demo
nano src/main.rs
作用域是绑定有效的区域。花括号会创建新的内部作用域。绑定 local_count 只存在于这对内部花括号之间,而 warehouse_count 属于外层的 main 作用域。
在步骤 1 的代码行中,只将 0 替换为 warehouse_count:
let local_count = warehouse_count; // Step 1: replace the placeholder.
整数类型 i32 实现了 Rust 的 Copy 行为。将它赋值给 local_count 时,Rust 会复制这个小值,而不会使 warehouse_count 失效。
使用 Ctrl+O 保存,按 Enter 确认,然后使用 Ctrl+X 退出。运行程序:
cargo run --quiet
前两行输出表明两个绑定都可以使用:
Local count: 3
Warehouse count: 3
内部作用域结束后,local_count 不再有效。外层的 warehouse_count 会一直有效到 main 结束。
阅读有意触发的移动错误
在本步骤中,你会有意在 String 被移动后继续使用它,然后解读 Rust 的编译器诊断信息。检查失败是预期结果,也是本步骤的学习目标。
String::from("PX-104") 会创建一个拥有所有权的文本值。与较小的 i32 不同,String 管理一个单独分配的文本缓冲区,其大小可以变化。Rust 会将这个缓冲区的责任交给一个所有者,从而确保它只被清理一次。
调用 record_parcel(parcel_id) 会按值传递 String。所有权从 parcel_id 绑定移动到函数中。函数返回这个值,调用代码将返回的所有者保存到 _returned_id 中。开头的下划线告诉 Rust:这个临时绑定目前有意不使用。
值不会因为移动而被销毁。只有旧绑定失去了使用它的权限。此程序中的所有权流转如下:
parcel_id in main → parcel_id parameter in record_parcel → _returned_id in main
在每个时刻,都有一个当前所有者负责同一段文本。
打开文件:
nano src/main.rs
找到步骤 2 注释下面的代码行,只删除行首的 //,使其变为:
println!("Original after move: {parcel_id}");
保存并退出。在运行下一个命令前,先预测结果:parcel_id 已经不再是所有者,因此编译器应该拒绝这次使用。
运行检查命令。该命令预期会失败:
cargo check
在诊断信息中找到以下稳定不变的部分:
error[E0382]: borrow of moved value: `parcel_id`
...
value moved here
...
value borrowed here after move
这里出现 borrow 一词,是因为 println! 只需要临时访问该值来显示它。由于所有权已经移动,通过 parcel_id 进行这种访问仍然不可能。下一步中,你将使用从函数接收回所有权的绑定。
继续使用返回的所有者
在本步骤中,你会为从 record_parcel 返回的值命名并使用它,从而修复移动错误。
函数签名 fn record_parcel(parcel_id: String) -> String 表示:函数接收一个 String 的所有权,并返回一个 String。函数末尾的 parcel_id 表达式会将所有权转移回调用者。
打开文件:
nano src/main.rs
进行两处小修改。首先,在函数调用所在的代码行中,将 _returned_id 重命名为 returned_id。因为你即将使用这个值,所以要去掉临时下划线。
然后,将导致失败的打印代码行替换为以下代码:
println!("Returned owner: {returned_id}");
移动之后,原来的 parcel_id 绑定仍然无效。将 _returned_id 重命名为 returned_id 并不能恢复旧绑定;这只是为已经接收回所有权的绑定提供一个普通名称。现在,returned_id 是当前所有者,因此应当使用这个名称。
保存并退出,然后检查修复后的程序:
cargo check
如果结果显示 Finished,且没有 E0382 错误,就说明所有权流转有效。运行程序:
cargo run --quiet
完整输出应为:
Local count: 3
Warehouse count: 3
Recorded parcel: PX-104
Returned owner: PX-104
在 main 结束时,returned_id 离开作用域,Rust 会清理它所拥有的 String。这段数据始终只有一个负责管理它的所有者。
总结
你将词法作用域与值的生命周期联系起来,观察了 i32 的复制不会使原始绑定失效,跟踪了 String 移动到函数中的过程,解读了 E0382,并使用返回给调用者的所有权安全地继续操作。



