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

03|所有权:谁负责保存和释放数据

目标:预测赋值与函数调用之后,哪些绑定仍然可以使用,并根据意图选择移动或克隆。

TextLab 将来会把文件读成一个 String。这个类型拥有可增长的 UTF-8 缓冲区;当拥有者被丢弃时,缓冲区通常随之释放。理解所有权的实用问题是:现在谁负责这份数据,以及谁还能访问它?

对于 String,按值赋给另一个绑定或传入函数通常会移动所有权。移动后不能继续用原绑定访问那个值。这里的“移动”描述语言规则,不保证机器执行了一次堆内容复制,也不承诺某种具体优化。整数等实现 Copy 的类型则允许隐式复制,原绑定仍可使用。

只有确实需要两份独立文本时才使用 clone()。它对 String 会复制字符串内容;如果只是想让函数读取数据,下一课的借用更合适。

看一次明确的交接

创建 examples/ownership.rs:

fn consume(text: String) -> usize {
    text.len()
}

fn main() {
    let original = String::from("rust");
    let moved = original;
    let backup = moved.clone();
    let bytes = consume(moved);
    println!("bytes={bytes} backup={backup}");

    let count: usize = 4;
    let copied = count;
    println!("count={count} copied={copied}");
}

String::from 从字面量创建拥有的字符串,len() 返回字节数。运行前先预测:为什么 backup 能在函数返回后使用,而 moved 不能?

cargo run --quiet --example ownership

预期输出:

bytes=4 backup=rust
count=4 copied=4

original 的值先交给 moved,然后 moved 的值交给函数参数 text。consume 读取长度,在函数返回时丢弃参数;返回的是独立的整数。backup 拥有另一份缓冲区,因此仍有效。第二段的整数赋值是复制,两个绑定都有效。

让编译器标出失效的那一步

  1. 在调用 consume(moved) 后面加入 println!("{moved}");。
  2. 运行 cargo check --example ownership,预期失败,诊断包含“borrow of moved value”一类说明,通常标记为 E0382。
  3. 找到诊断中“发生移动的位置”和“移动后使用的位置”,它们不是同一行。
  4. 删除新增行,再运行原命令恢复成功状态。

独立练习:实现 add_suffix(mut text: String) -> String,追加 "!" 后把字符串所有权返回。调用者继续使用返回值,整个过程不调用 clone()。

参考实现与原因

完整替换实验文件即可运行:

fn add_suffix(mut text: String) -> String {
    text.push_str("!");
    text
}

fn main() {
    let text = String::from("rust");
    let text = add_suffix(text);
    assert_eq!(text, "rust!");
    println!("{text}");
}

预期输出 rust!。第二个 let text 是遮蔽:建立新绑定接住函数返回的拥有值。mut 允许函数更新字符串,返回值把释放责任移回调用者。

如果你总靠 clone() 让编译通过,先把数据交接画成“调用者 → 参数 → 返回值”,检查是否真的需要第二份数据。如果误以为 mut 能修复移动错误,记住它仅允许修改当前绑定或通过它修改拥有的数据,不会恢复已经移出的值。若调用 len() 得到与你数的汉字不同的结果,第 5 课会解释统计单位。

保留 examples/ownership.rs;主项目还没有接入字符串处理,避免把语言实验与项目功能混在一起。

参考资料