TT Lab
开始
学习 学习路径 课程

Rust — 编译器拦下的那些

和编译器对话

在 TT Lab 中继续学习

目标

通过阅读错误消息,学会 Rust 编译器拦下了什么、为什么拦。本实验里有故意让构建失败的步骤——因为那些消息正是要学的东西。

为什么重要

让 Rust 变难的不是语法,而是所有权。而学习所有权最快的方法,就是被编译器拦几次。

被拦下的这两件事,其实是未来的 bug。

在 C++ 里,两者都会悄无声息地通过,之后冒出奇怪的值。

环境

没有网络,所以 cargo add 无法工作。本实验只使用标准库,所以没有影响。

cargo build --offline     빌드
cargo run --offline       실행
cargo clippy --offline    더 나은 방법 제안

步骤

所有文件都创建在 /root/work/own 内。

  1. cargo init --name own
  2. examples/move_err.rs ——故意失败 → 02-move.txt
  3. examples/move_ok.rs ——按 help 修复
  4. examples/borrow_err.rs ——故意失败 → 04-borrow.txt
  5. examples/borrow_ok.rs ——缩小范围使其通过
  6. 用 Result 处理错误 → 06-result.txt
  7. clippy → 07-clippy.txt
  8. 什么被拦下了、为什么 → 08-notes.md

为什么是 examples/ ——cargo build 不会动 examples。把故意写坏的代码放在那里,整个项目的构建就仍然可以通过。

参考

创建项目

在 /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 现在就展示给你看了(访问已释放的内存、重新分配之后的向量的旧指针)。