和编译器对话
目标
通过阅读错误消息,学会 Rust 编译器拦下了什么、为什么拦。本实验里有故意让构建失败的步骤——因为那些消息正是要学的东西。
为什么重要
让 Rust 变难的不是语法,而是所有权。而学习所有权最快的方法,就是被编译器拦几次。
被拦下的这两件事,其实是未来的 bug。
E0382(moved value)——使用已被释放的内存的事故E0502(borrow conflict)——容器被重新分配之后,读取旧指针的事故
在 C++ 里,两者都会悄无声息地通过,之后冒出奇怪的值。
环境
没有网络,所以 cargo add 无法工作。本实验只使用标准库,所以没有影响。
cargo build --offline 빌드
cargo run --offline 실행
cargo clippy --offline 더 나은 방법 제안
步骤
所有文件都创建在 /root/work/own 内。
cargo init --name ownexamples/move_err.rs——故意失败 →02-move.txtexamples/move_ok.rs——按 help 修复examples/borrow_err.rs——故意失败 →04-borrow.txtexamples/borrow_ok.rs——缩小范围使其通过- 用
Result处理错误 →06-result.txt - clippy →
07-clippy.txt - 什么被拦下了、为什么 →
08-notes.md
为什么是 examples/ ——cargo build 不会动 examples。把故意写坏的代码放在那里,整个项目的构建就仍然可以通过。
参考
- 请把错误消息读到最后。
help:一行里有修复方法。 - 和所有权较劲卡住时,可以
clone()一下继续往前。等性能成为问题时再去修——从一开始就想用引用做优化,是让 Rust 显得困难的最常见原因。
创建项目
在 /root/work/own 中创建 cargo 项目,并让它在空状态下构建通过。
mkdir -p /root/work/own && cd /root/work/own && cargo init --name own。没有网络,所以不要用 cargo add——本实验只使用标准库。始终加上 --offline:cargo build --offline。
故意移动一次,读懂错误
在 examples/move_err.rs 中写一段再次使用已被转移的值的代码,并把完整的构建错误保存到 02-move.txt。这一步的正确答案是构建失败。
先 mkdir -p examples,然后在 examples/move_err.rs 里写一个 main,在其中写 let s = String::from("hi"); let t = s; println!("{} {}", s, t);。再执行 cargo build --offline --example move_err > 02-move.txt 2>&1。必须能看到 E0382 和“moved”。examples/ 不会被 cargo build 碰到,所以留着坏掉也没关系。
按编译器说的去修
在 examples/move_ok.rs 中写出不出错地完成同一件事的版本,运行后要打印出两个字符串。
错误消息下面的 help: 一行会告诉你怎么修。可以用 s.clone() 复制,或者用 &s 借用来通过——两种都是正确答案。cargo run --offline --example move_ok。
撞一撞借用规则
在 examples/borrow_err.rs 中写一段在只读借用还活着的时候执行 push 的代码,并把错误保存到 04-borrow.txt。这里的正确答案也是失败。
let mut v = vec![1,2,3]; let first = &v[0]; v.push(4); println!("{}", first); → cargo build --offline --example borrow_err > 04-borrow.txt 2>&1。会出现 E0502——意思是只读借用还活着,却要求了可写借用。
缩小范围使其通过
在 examples/borrow_ok.rs 中写出做同一件事、但构建通过的版本。既要 push,也要打印第一个值。
借用只活到最后一次被使用的位置。在 push 之前把 first 用完,冲突就消失了。先把值复制下来(let first = v[0];)也是正确答案——因为 i32 是 Copy。
用 Result 处理错误
编写 fn half(n: i32) -> Result<i32, String>,对奇数返回 Err,并在 main 中把成功和失败两种情况都打印出来。把结果保存到 06-result.txt。
放进 src/main.rs,用 match half(n) { Ok(v) => ..., Err(e) => ... } 把两个分支都处理。不要用 unwrap()——那是在失败时让进程崩溃的 panic。cargo run --offline > 06-result.txt 2>&1。
向 clippy 请教
让 cargo clippy --offline 无警告通过,并把输出保存到 07-clippy.txt。
用 cargo clippy --offline --all-targets -- -D warnings > 07-clippy.txt 2>&1 把警告提升为错误来确认。examples/ 里故意写坏的文件必须排除,所以不要用 --all-targets,只看默认目标:cargo clippy --offline > 07-clippy.txt 2>&1。如果出现提示,请读懂它在指出什么、为什么,然后修好。
什么被拦下了,为什么
在 08-notes.md 中用三行整理——E0382 拦下的是什么,E0502 拦下的是什么,以及如果没有这两个,运行中会出什么事故。
请把两个错误码原样写进正文。第三行是关键:编译器是把未来的 bug 现在就展示给你看了(访问已释放的内存、重新分配之后的向量的旧指针)。