练习与自测
本章练习共 5 题,答案折叠在每题下方。建议先自己写、编译通过后再展开答案;难度标记:★☆☆ 基础 / ★★☆ 综合 / ★★★ 挑战。
本章练习是「环境与学习准备」性质的,重点是让工具链和习惯就位。
练习 1:第一个 Cargo 工程
难度:★☆☆
要求:建立一个名为 hello_rust 的工程,改写成输出两行:一行英文问候,一行中文问候(含你的名字),并分别用 cargo run 和 cargo run --release 运行,观察 target/ 目录下的产物差异。
提示:注意 target/debug/ 与 target/release/。
参考答案(先自己写再看)
powershell
mkdir rust-practice
cd rust-practice
cargo new hello_rust
cd hello_rustsrc/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,开启调试断言);--release是opt-level = 3。- 整数溢出行为不同:debug 下溢出会 panic,release 下会环绕(详见 变量与流程控制)。这不是小事,调 bug 时务必在 debug 下复现。
{name}是内联格式参数(1.58+ 稳定),等价于println!("hello, {}!", name);比{}更短且不会出现参数顺序错误。
练习 2:工具链自检与组件安装
难度:★☆☆
要求:用一条命令确认本机 rustc、cargo 版本与默认工具链,并把结果记在你的笔记里。然后回答:如果 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/cargo 由 rustup 管理的工具链提供,版本号必然同源。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 在堆上有缓冲区,如果 s 和 t 都能用,程序结束时就会 free 两次——Rust 直接在编译期禁止了这种可能。〈所有权〉一章会完整讲 move / Copy / 借用的判定规则。
练习 4:编辑器与开发环境配置
难度:★★☆
要求:你打算用的编辑器是 VS Code。列出让 Rust 开发体验正常所需的至少 3 项配置或扩展,并说明每一项解决什么问题。
提示:语言服务、保存即格式化、内联类型提示(inlay hints)。
参考答案(先自己写再看)
- 扩展
rust-analyzer(官方推荐,替代已废弃的 Rust 扩展):提供补全、跳转定义、错误实时提示、cargo check集成。注意它需要rustup component add rust-analyzer或使用扩展自带二进制。 - 保存时格式化:
settings.json中开启
json
{
"editor.formatOnSave": true,
"[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" }
}- 内联类型提示(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
}- (加分)
cargo check而非cargo build做快速反馈:rust-analyzer.check.command设为"clippy"可以让编辑器里直接显示 clippy 建议。
要点解析:rust-analyzer 与 cargo 是配合关系:前者负责 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 的两条基本设计线。