Skip to content

练习与自测

本章练习共 5 题,答案折叠在每题下方。建议先自己写、编译通过后再展开答案;难度标记:★☆☆ 基础 / ★★☆ 综合 / ★★★ 挑战。

本章练习是「环境与学习准备」性质的,重点是让工具链和习惯就位。

练习 1:第一个 Cargo 工程

难度:★☆☆

要求:建立一个名为 hello_rust 的工程,改写成输出两行:一行英文问候,一行中文问候(含你的名字),并分别用 cargo runcargo run --release 运行,观察 target/ 目录下的产物差异。

提示:注意 target/debug/target/release/

参考答案(先自己写再看)
powershell
mkdir rust-practice
cd rust-practice
cargo new hello_rust
cd hello_rust

src/main.rs

rust
fn main() {
    let name = "Rust 学习者";
    println!("hello, {name}!");
    // 中文输出完全没问题:源码本身必须是 UTF-8,println! 写到 stdout 也是 UTF-8
    println!("你好,{name}!这是你的第一个 Rust 程序。");
}

运行:

powershell
cargo run             # 产物:target/debug/hello_rust.exe(含调试符号,未优化)
cargo run --release   # 产物:target/release/hello_rust.exe(优化后,体积与速度都不同)
Get-ChildItem target -Recurse -Filter hello_rust.exe | Select-Object FullName, Length

要点解析

  • cargo run 默认是 debug profile(opt-level = 0,开启调试断言);--releaseopt-level = 3
  • 整数溢出行为不同:debug 下溢出会 panic,release 下会环绕(详见 变量与流程控制)。这不是小事,调 bug 时务必在 debug 下复现。
  • {name} 是内联格式参数(1.58+ 稳定),等价于 println!("hello, {}!", name);比 {} 更短且不会出现参数顺序错误。

练习 2:工具链自检与组件安装

难度:★☆☆

要求:用一条命令确认本机 rustccargo 版本与默认工具链,并把结果记在你的笔记里。然后回答:如果 rustup component list --installed 里没有 clippy,应该执行什么命令?

提示rustup 负责工具链,cargo 负责工程。

参考答案(先自己写再看)
powershell
rustc --version; cargo --version; rustup show

典型输出(Windows 默认安装,版本号以你本机为准):

rustc 1.98.1 (48a229cea 2026-09-01)
cargo 1.98.1 (797e8a9bc 2026-08-05)
Default host: x86_64-pc-windows-msvc
rustup home:  C:\Users\<你>.rustup

installed toolchains
--------------------
stable-x86_64-pc-windows-msvc (active, default)

缺少 clippy 时:

powershell
rustup component add clippy

要点解析rustc/cargorustup 管理的工具链提供,版本号必然同源。rustup component add 会把组件装到当前活动工具链下;若你有多个工具链,可加 --toolchain stable 明确指定。

练习 3:判断 move 导致的编译错误

难度:★★☆

要求:判断下面这段代码能否编译通过,并说明理由;如果不能,给出最小修改。

rust
fn main() {
    let s = String::from("hello");
    let t = s;
    println!("{s} {t}");
}

提示String 是堆分配类型,不实现 Copy

参考答案(先自己写再看)

不能编译。 报错:

error[E0382]: borrow of moved value: `s`
 --> src/main.rs:4:15
  |
2 |     let s = String::from("hello");
  |         - move occurs because `s` has type `String`,
  |           which does not implement the `Copy` trait
3 |     let t = s;
  |             - value moved here
4 |     println!("{s} {t}");
  |                ^ value borrowed here after move

修改方案(三选一):

rust
// 方案 1:克隆,两处都能用(有额外分配成本)
fn main() {
    let s = String::from("hello");
    let t = s.clone();
    println!("{s} {t}");
}

// 方案 2:借用,不转移所有权(推荐,零成本)
fn main() {
    let s = String::from("hello");
    let t = &s;
    println!("{s} {t}");
}

// 方案 3:只在最后用一次,交换使用顺序(把 s 用完再移动)
fn main() {
    let s = String::from("hello");
    println!("{s}");
    let t = s;
    println!("{t}");
}

要点解析:这是 Rust 与几乎所有主流语言最不同的地方。String 在堆上有缓冲区,如果 st 都能用,程序结束时就会 free 两次——Rust 直接在编译期禁止了这种可能。〈所有权〉一章会完整讲 move / Copy / 借用的判定规则。

练习 4:编辑器与开发环境配置

难度:★★☆

要求:你打算用的编辑器是 VS Code。列出让 Rust 开发体验正常所需的至少 3 项配置或扩展,并说明每一项解决什么问题。

提示:语言服务、保存即格式化、内联类型提示(inlay hints)。

参考答案(先自己写再看)
  1. 扩展 rust-analyzer(官方推荐,替代已废弃的 Rust 扩展):提供补全、跳转定义、错误实时提示、cargo check 集成。注意它需要 rustup component add rust-analyzer 或使用扩展自带二进制。
  2. 保存时格式化settings.json 中开启
json
{
  "editor.formatOnSave": true,
  "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" }
}
  1. 内联类型提示(inlay hints):Rust 大量依赖类型推断,打开 inlay hints 能直接看到 let v = Vec::new() 推断出的具体类型,学习期非常有用:
json
{
  "rust-analyzer.inlayHints.typeHints.enable": true,
  "rust-analyzer.inlayHints.parameterHints.enable": true,
  "rust-analyzer.inlayHints.chainingHints.enable": true
}
  1. (加分)cargo check 而非 cargo build 做快速反馈rust-analyzer.check.command 设为 "clippy" 可以让编辑器里直接显示 clippy 建议。

要点解析rust-analyzercargo 是配合关系:前者负责 IDE 体验,后者负责真实构建。两者版本不一致时可能出现「编辑器不报错但 cargo build 失败」,此时以 cargo build 为准。

练习 5:越界访问为何是 panic 而非编译错误

难度:★★★

要求:不查资料,先写下你的猜测:下面这段代码在 Rust 里是编译错误还是能运行但可能 panic?然后运行验证,并解释为什么 Rust 选择这个设计。

rust
fn main() {
    let v = vec![1, 2, 3];
    println!("{}", v[10]);
}

提示:想想数组下标在 C 语言里意味着什么。

参考答案(先自己写再看)

能编译,运行期 panic

thread 'main' panicked at src/main.rs:3:20:
index out of bounds: the len is 3 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

为什么不是编译错误:v[10] 的下标是运行期才知道的值,编译器无法静态证明它越界(若下标来自用户输入或计算结果,静态检查就不可判定)。Rust 的态度是:不能静态保证的,就用运行期检查兜底,且不允许静默越界

想避免 panic 的写法:

rust
fn main() {
    let v = vec![1, 2, 3];

    // 方式 1:get 返回 Option,越界得到 None
    match v.get(10) {
        Some(x) => println!("{x}"),
        None => println!("下标越界,但没有崩溃"),
    }

    // 方式 2:提供默认值
    println!("{}", v.get(10).copied().unwrap_or(-1));
}

要点解析

  • C/C++ 的 v[10]未定义行为(可能读到垃圾、可能崩溃、可能被优化器做出任意推断),Rust 把它变成确定的 panic
  • get 返回 Option<&T> 是「可失败访问」的惯用 API,〈集合与迭代器〉一章会系统讲。
  • 这与上一条 move 规则形成对比:能静态检查的一律静态拒绝,不能静态检查的用运行期检查 + Option/Result 显式表达。这就是 Rust 的两条基本设计线。

本章小结 / 自测清单


内容以 rustc 1.98.1 · Rust 2024 edition 为基准