Course Builder个人学习工作台 · 本地模式
Rust:从所有权到可测试的命令行工具

04|借用:让函数使用数据而不接管它

目标:为只读和修改两种操作设计不同的引用参数,并解释借用冲突。

上课的 consume 接管文本,这对统计函数太强了:统计结束后,调用者还需要显示或继续分析文本。引用让函数临时访问数据,所有权留在原处。

&T 表示共享引用,通过它通常只能读取;&mut T 表示独占的可变引用。在这些普通借用中,同一数据在借用有效期间可以有多个共享引用,或一个可变引用,但不能把相冲突的访问同时保留。这样,当你读某段字符串时,另一个操作就不能通过可变借用改变其缓冲区,使读取建立在失效的位置上。

借用是否还有效与它后面是否被使用有关,不一定延续到大括号结束。这个区别能解释很多“明明在同一作用域,却可以先读再改”的程序。

接口的访问权限写在参数里

创建 examples/borrowing.rs:

fn byte_count(text: &str) -> usize {
    text.len()
}

fn append_mark(text: &mut String) {
    text.push_str("!");
}

fn main() {
    let mut text = String::from("rust");
    let view = &text;
    println!("before={}", byte_count(view));
    append_mark(&mut text);
    println!("after={} text={text}", byte_count(&text));
}

这里 &str 是借来的文本视图,下课细讲;&String 在需要 &str 的位置可以自动转换。修改函数使用 &mut String,因为它需要增长字符串,而不仅是看已有字节。

运行前先预测:第一次输出后,view 还会在后面被用到吗?

cargo run --quiet --example borrowing

预期输出:

before=4
after=5 text=rust!

第一次输出是 view 最后一次使用,接着可以独占借用 text。append_mark 返回后,它的可变借用结束,最后又可以共享借用。println! 中的 {} 按顺序接收后面的表达式,也能与 {text} 混用。

延长读取会发生什么

  1. 在 append_mark(&mut text); 后面加上 println!("old view={view}");。
  2. 运行 cargo check --example borrowing,应出现共享借用与可变借用冲突,通常是 E0502。
  3. 把新增输出移动到 append_mark 之前,应恢复编译成功。
  4. 删除新增输出,恢复主示例。

错误不是说“一个变量不能先读再改”,而是说你安排了在可变访问之后继续使用此前的共享借用。定位引用的最后一次使用,比盲目添加大括号更有帮助。

独立练习:写 append_if_empty(text: &mut String),只在字符串为空时追加 "untitled"。在同一个 main 中验证空串会改变,已有内容不变。is_empty() 返回是否没有内容。

完整参考实现
fn append_if_empty(text: &mut String) {
    if text.is_empty() {
        text.push_str("untitled");
    }
}

fn main() {
    let mut empty = String::new();
    let mut named = String::from("notes");
    append_if_empty(&mut empty);
    append_if_empty(&mut named);
    assert_eq!(empty, "untitled");
    assert_eq!(named, "notes");
    println!("{empty} / {named}");
}

替换实验文件后运行同一命令,预期 untitled / notes。String::new() 创建空字符串。函数没有返回新字符串,调用者原有的字符串被修改了。

如果出现“cannot borrow as mutable”,同时检查原绑定的 mut 和调用处的 &mut,两者承担不同角色。如果共享引用与可变引用冲突,按顺序标出创建、修改、最后读取的位置,不要先尝试克隆整个对象。TextLab 的统计接口将接收 &str,因为统计不需要改变输入,也不需要接管输入。

本课新增 examples/borrowing.rs,不修改主入口。

参考资料