写 Rust 最让人头秃的时候,大概就是觉得自己逻辑完美,编译器却甩给你一串红字的 cannot borrow as mutable

你只是想用 s.clear() 清空个字符串而已,招谁惹谁了?这时候的 Rust 就像个死板的图书管理员,死活不让你动手。

但这真不是在针对你。我们换个角度,用**“正在读的书能不能撕”**这个例子,来看看这报错到底在保护什么。

案发现场

假设你想找出字符串的第一个单词,用完之后把字符串清空省点内存。代码大概长这样:

fn main() {
    let mut s = String::from("hello world");

    // 1. 借出一个不可变引用(word 是 s 的一个切片)
    let word = first_word(&s);

    // 2. 试图清空字符串(需要可变借用)
    s.clear(); // ❌ 报错!

    // 3. 再次使用那个不可变引用
    println!("the first word is: {}", word);
}

fn first_word(s: &String) -> &str {
    // ...省略具体实现,返回 s 的一个切片
    &s[0..5]
}

编译器马上跳出来警告:

cannot borrow 's' as mutable because it is also borrowed as immutable

你可能会嘀咕:“我就清空一下 s,跟 word 有什么关系?”

图书馆里不准撕书

s 想象成图书馆里的一本孤本

  1. 不可变借用 (&s):这就是在馆内阅读。图书馆允许多个人同时阅读(多个人持有不可变引用),大家围在桌子旁一起看,没问题。这时候,word 就是你抄在手心的小抄(比如:这本书的第1页到第5页)。

  2. 可变借用 (&mut s):这就是 s.clear() 想干的事——修改内容,甚至把书撕成白纸。

为什么冲突?

想象一下,你正拿着小抄(word)读得津津有味,突然冲过来一个人要撕书(s.clear())。

如果他真撕了,你手里的小抄指向的就不是书页,而是空气。这时候再去读(println!),程序要么崩,要么读出一堆乱码。

Rust 的铁律很简单:

有人读的时候,谁也别想改。 (Readers exclude Writers)

读写互斥原理图

怎么解决?

既然是“读写冲突”,解决办法就两个:要么读完再改,要么各玩各的。

1. 读完再改(调整位置)

这是最简单的。既然 word 还要用(在 println! 里),那就等用完再说。只要确保 s.clear()word 谢幕之后就行。

fn main() {
    let mut s = String::from("hello world");

    // 作用域开始:word 借用了 s
    let word = first_word(&s);

    // 在这里先使用 word,用完之后,Rust 的 NLL (Non-Lexical Lifetimes) 机制
    // 会判定 word 的生命周期结束了
    println!("the first word is: {}", word);

    // 此时 s 身上已经没有借用了,可以随意修改
    s.clear(); // ✅ 编译通过!
}

类比一下:你看完书,笔记做完了,人也走了。这时候管理员再来撕书(清理),谁也不会受影响。

2. 自己复印一份(Clone)

如果你非要在 s.clear() 之后还能使用那个单词,那就别借书看了,自己复印一份带走。

fn main() {
    let mut s = String::from("hello world");

    // 不借用 s 的引用,而是把切片转换成全新的 String
    let word = first_word(&s).to_string();

    s.clear(); // s 被清空了

    // word 是自己复印的副本,跟 s 没关系了,随便用
    println!("the first word is: {}", word); // ✅ 编译通过!
}

Clone 方案可视化

代价to_string()clone() 得复印数据,稍微多费点内存(买书比借书贵),但这俩变量从此就没瓜葛了。

常见坑

隐式借用

有时候你没写 &,但有些方法悄悄借用了。

let len = s.len(); // len() 只是读取,短暂不可变借用,瞬间结束
s.clear(); // 没问题

Debug 打印

新手常犯的毛病:为了调试看变量,结果不小心把生命周期拖长了。

let word = first_word(&s);
s.clear();
// 这一行原本是为了调试,结果导致了上面的 clear 报错
// 删掉这行,或者把它移到 clear 之前,代码就能跑了
dbg!(word);

总结一下

记句口诀,Rust 借用不再难:

共享不可变,可变不共享。 要想撕书,等读者走光。

救命小抄

遇到 cannot borrow as mutable

  1. 查引用:这行之前,谁借了 &s
  2. 看结尾:那个借用(比如 word)最后一次在哪用的?
  3. 挪位置:能把修改操作(s.clear())挪到最后一次使用后面吗?
  4. 大招:实在不行,.clone() 吧。

觉得这篇文章有用吗?

  1. 点赞:如果觉得“撕书”这个比喻让你秒懂,点个赞支持一下!
  2. 转发:分享给身边正在和 Rust 编译器搏斗的朋友。
  3. 关注:关注梦兽编程,每天一个生活案例解锁技术难题。
  4. 留言:你还遇到过哪些让你抓狂的 Rust 报错?欢迎评论区吐槽,下一篇没准就是为你写的!

你的支持是我持续创作的最大动力!