借助引用借用数据

RustBeginner
立即练习

简介

移动 String 会转移所有权,但许多函数只需要临时访问数据。Rust 将这种临时访问称为借用,并使用引用来表示借用。

你将分三个小步骤完成一个准备好的访客列表程序:先通过共享引用检查由调用方拥有的文本,然后有意观察共享访问为什么不能修改文本,最后通过可变引用执行一次受控更新。最终输出将证明 main 仍然拥有并可以使用该列表。

通过共享引用检查数据

在此步骤中,你将把 String 的只读访问权借给函数,而不是把所有权移动给函数。

进入准备好的 Cargo 项目并打开源文件:

cd /home/labex/project/guest-borrowing
nano src/main.rs

初始化过程已经创建了完整项目,并在调用 inspect_guests 的位置留下了一个占位符。该函数的参数类型是 &String。其中的 & 表示「指向 String 的引用」:函数可以临时查看这个值,但不会成为它的所有者。

只将 /* Step 1: borrow guest_list */ 替换为 &guest_list,使调用变为:

    inspect_guests(&guest_list);

这里的 &guest_list 会创建一个共享引用。共享引用表示可以共享只读访问,而不会转移 String 本身的所有权。

按 Ctrl+O 保存,按 Enter 确认,然后按 Ctrl+X 退出。运行程序:

cargo run --quiet

输出应为:

Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo

第一行来自借用的引用。第二行是重要的所有权证据:inspect_guests 返回后,main 仍然可以使用 guest_list。更新函数也会读取当前值,但此时还不会修改它。

观察共享引用的限制

在此步骤中,你将有意尝试通过共享引用修改 String。编译器检查失败是预期结果,它可以帮助你理解 Rust 的借用规则。

再次打开源文件:

nano src/main.rs

在 add_guest 中,只删除准备好代码行开头的 //,使其变为:

    list.push_str(", Mina");

push_str 方法会向 String 追加文本,因此需要获得修改该值的权限。但是,list 当前的类型是 &String,它只提供共享的只读访问权限。

保存并退出。在检查之前先预测结果:编译器应拒绝这次修改,因为共享引用不能提供可变访问权限。

运行检查命令。此时失败是预期结果:

cargo check

在诊断信息中找到以下稳定不变的部分:

error[E0596]: cannot borrow `*list` as mutable, as it is behind a `&` reference
...
consider changing this to be a mutable reference

*list 表示通过引用访问到的 String。Rust 并不是说这个 String 永远不能改变,而是说当前这个 &String 没有携带修改它的权限。下一步将为函数提供受控的可变访问权限。

通过可变引用更新数据

在此步骤中,你将为 add_guest 提供临时且独占的权限,使其可以修改调用方的 String。

可变引用的形式是 &mut T。一个适合初学者记忆的规则是:可以同时有多个读取者,但同一时间只能有一个写入者。共享引用允许读取,而一个可变引用会提供临时的独占写入权限。这种独占性可以防止程序的不同部分意外地同时修改同一个值。

打开文件:

nano src/main.rs

完成以下三处修改:

  • 将 add_guest 的参数从 &String 改为 &mut String。
  • 在绑定中添加 mut,使其变为 let mut guest_list = ...。
  • 将 add_guest 的调用从 &guest_list 改为 &mut guest_list。

完成后的函数签名和调用应分别如下:

fn add_guest(list: &mut String) {
    add_guest(&mut guest_list);

这里的两个 mut 用途相关但并不相同。let mut guest_list 表示允许修改这个由调用方拥有的值。随后,&mut guest_list 会创建一个临时的可变引用,在调用期间把独占写入权限借给 add_guest。

保存并退出,然后检查修改结果:

cargo check

如果结果显示 Finished 且没有 E0596,说明当前权限设置已经匹配。运行程序:

cargo run --quiet

最终输出应为:

Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo, Mina

add_guest 返回时,可变借用就结束了。最后一次打印可以再次读取 guest_list,这证明所有权仍然保留在 main 中,同时函数完成了一次受控更新。

总结

你使用 &String 获得共享的只读访问权限,使用 &mut String 执行临时且独占的修改。在这两种情况下,调用方都保留了所有权;编译器则帮助你区分对借用数据的查看和修改。