调试与错误
> 认识常见编译错误信息的结构,掌握利用编译器提示快速定位和修复问题的方法。
常见错误
错误 1:修改变量忘记 mut
概念名称: 尝试修改不可变变量会触发编译错误 E0384。
错误信息:
┌──────────────────────────────────────┐
│ error[E0384]: cannot assign twice │
│ to immutable variable `x` │
│ │
│ E0384 = "不能给不可变变量赋值两次" │
│ 解决方案:添加 mut 使变量可变 │
└──────────────────────────────────────┘最简示例
rust
▶ Runfn main() {
let x = 10;
// x = 20; // ❌ E0384: 不能修改不可变变量
let mut y = 10; // ✅ 正确
y = 20;
println!("y = {}", y);
}详细示例
rust
▶ Runfn main() {
// 错误场景:忘记 mut
let count = 0;
// count += 1; // ❌ 编译错误:不能修改不可变变量
// 修复方案 1:添加 mut
let mut count = 0;
count += 1;
println!("count = {}", count);
// 修复方案 2:使用遮蔽
let count = 0;
let count = count + 1; // 新变量遮蔽旧变量
println!("count = {}", count);
}关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
let x = 10 | 不可变变量 | Rust 默认不可变,安全优先 |
let mut y = 10 | 可变变量 | 显式声明可变,明确意图 |
let count = count + 1 | 遮蔽 | 不修改原值,创建新值 |
错误 2:变量未初始化就使用
概念名称: 使用未初始化的变量会触发编译错误 E0381。
错误信息:
┌──────────────────────────────────────┐
│ error[E0381]: used binding `x` is │
│ not initialized │
│ │
│ E0381 = "使用了未初始化的变量" │
│ 解决方案:使用前必须赋值 │
└──────────────────────────────────────┘最简示例
rust
▶ Runfn main() {
let x: i32;
// println!("{}", x); // ❌ E0381: 未初始化
}详细示例
rust
▶ Runfn main() {
// 错误场景:声明但未初始化
let x: i32;
// println!("{}", x); // ❌ 编译错误
// 修复方案 1:立即初始化
let x: i32 = 0;
println!("x = {}", x);
// 修复方案 2:条件初始化
let x: i32;
let condition = true;
if condition {
x = 10;
} else {
x = 0;
}
println!("x = {}", x); // ✅ 所有分支都初始化了
}关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
let x: i32 | 声明未初始化 | 必须标注类型,编译器无法推断 |
x = 10 | 延迟初始化 | 所有代码路径必须初始化 |
错误 3:类型推断失败
概念名称: 编译器无法推断变量类型时需要显式标注。
错误信息:
┌──────────────────────────────────────┐
│ error[E0282]: type annotations needed│
│ │
│ E0282 = "需要类型标注" │
│ 解决方案:显式标注类型 │
└──────────────────────────────────────┘最简示例
rust
▶ Runfn main() {
let x: i32; // ✅ 显式标注类型
x = 10;
println!("x = {}", x);
}详细示例
rust
▶ Runfn main() {
// 错误场景:无法推断类型
// let x; // ❌ 编译器不知道 x 是什么类型
// x = 10;
// 修复方案 1:显式标注
let x: i32;
x = 10;
// 修复方案 2:声明时初始化
let x = 10; // ✅ 推断为 i32
// 修复方案 3:使用默认值
let x = i32::default(); // 0
println!("x = {}", x);
}关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
let x: i32 | 显式类型标注 | 编译器无法推断时需要 |
let x = 10 | 类型推断 | i32 是默认整数类型 |
i32::default() | 默认值 | 0,适合初始化 |
调试技巧
使用 dbg! 宏
概念名称: dbg! 宏打印表达式及其结果,包含文件名和行号。
语法结构:
┌──────────────────────────────────────┐
│ dbg!(表达式); │
│ │
│ 输出:[文件名:行号] 表达式 = 结果 │
│ 返回值:表达式的值 │
└──────────────────────────────────────┘为什么用它?
rust
▶ Run// 没有:手动打印,缺少上下文
let sum = x + y;
println!("sum = {}", sum); // 不知道在哪打印的
// 有:自动包含文件名和行号
let sum = dbg!(x + y);
// 输出:[src/main.rs:5] x + y = 30最简示例
rust
▶ Runfn main() {
let x = 10;
let y = 20;
let sum = dbg!(x + y);
println!("sum = {}", sum);
}详细示例
rust
▶ Runfn main() {
let x = 10;
let y = 20;
// dbg! 打印表达式和结果
let sum = dbg!(x + y);
// 输出:
// [src/main.rs:5] x + y = 30
// 可以链式使用
let result = dbg!(dbg!(x * 2) + dbg!(y * 3));
println!("result = {}", result);
}关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
dbg!(x + y) | 调试打印 | 自动打印文件名、行号、结果 |
dbg!(dbg!(x)) | 链式调试 | 可嵌套,追踪中间值 |
| 返回值 | 返回表达式值 | 不影响原代码逻辑 |
使用 cargo expand
安装:
bash
cargo install cargo-expand查看宏展开:
bash
cargo expand小结
本章我们学习了:
- ✅
println!宏的详细用法 - ✅ 变量声明和可变性
- ✅ 类型推断和显式标注
- ✅ 常量和静态变量
- ✅ 变量遮蔽
- ✅ 注释的写法
关键概念
| 概念 | 说明 |
|---|---|
let | 声明变量 |
let mut | 声明可变变量 |
const | 常量(必须标注类型) |
println! | 打印宏(带换行) |
print! | 打印宏(不带换行) |
| 遮蔽 | 重新使用变量名 |
dbg! | 调试宏(打印表达式和结果) |
练习题
详见:练习题