🐛 Debug Rust Code Hiệu Quả
Debugging trong Rust khác biệt - compiler giúp bạn rất nhiều! Học cách tận dụng các công cụ để debug nhanh và hiệu quả.
Trong Rust: Nếu compile được, thường sẽ chạy đúng!
Phần lớn bugs được bắt lúc compile-time, không phải runtime. 🎯
🎯 Debug Strategy
Level 1: Compiler Messages (Bắt 80% lỗi)
Compile → Read errors → Fix → Repeat
Level 2: println! Debugging (Bắt 15% lỗi)
Add prints → Run → Analyze output → Fix
Level 3: Debugger (Bắt 5% lỗi còn lại)
Set breakpoints → Step through → Inspect → Fix
📖 Level 1: Hiểu Compiler Messages
The Rust Compiler Là Mentor Tốt Nhất!
ELI5: Compiler như thầy giáo kiên nhẫn - chỉ ra lỗi và cách sửa! 👨🏫
Error Message Anatomy
fn main() {
let s = String::from("hello");
let s2 = s;
println!("{}", s); // ❌
}
Compiler output:
error[E0382]: borrow of moved value: `s`
--> src/main.rs:4:20
|
2 | let s = String::from("hello");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s;
| - value moved here
4 | println!("{}", s);
| ^ value borrowed here after move
|
= note: consider cloning the value if the performance cost is acceptable: `s.clone()`
For more information about this error, try `rustc --explain E0382`.
Phân tích từng phần:
-
Error code:
E0382# Xem giải thích chi tiết
rustc --explain E0382 -
Error message:
borrow of moved value- Mô tả vấn đề ngắn gọn
-
Location:
src/main.rs:4:20- File, line, column chính xác
-
Explanation với arrows:
2 | let s = String::from("hello");
| - move occurs because...
3 | let s2 = s;
| - value moved here
4 | println!("{}", s);
| ^ value borrowed here after move- Chỉ rõ điều gì xảy ra ở đâu
-
Note/Help:
= note: consider cloning the value if the performance cost is acceptable- Gợi ý cách fix!
Common Error Codes
E0382: Borrow of Moved Value
Vấn đề: Dùng value đã bị move
// ❌ Lỗi
let s = String::from("hello");
let s2 = s; // s moved to s2
println!("{}", s); // Error!
Fix options:
// ✅ Option 1: Clone
let s = String::from("hello");
let s2 = s.clone();
println!("{}", s); // OK!
// ✅ Option 2: Borrow
let s = String::from("hello");
let s2 = &s;
println!("{}", s); // OK!
// ✅ Option 3: Use s2
let s = String::from("hello");
let s2 = s;
println!("{}", s2); // OK!
E0502: Cannot Borrow As Mutable
Vấn đề: Cố mượn mutable khi đã có borrow khác
// ❌ Lỗi
let mut v = vec![1, 2, 3];
let first = &v[0]; // Immutable borrow
v.push(4); // Mutable borrow - Error!
println!("{}", first);
Fix:
// ✅ Drop immutable borrow trước
let mut v = vec![1, 2, 3];
{
let first = &v[0];
println!("{}", first);
} // first dropped here
v.push(4); // OK!
E0499: Cannot Borrow As Mutable More Than Once
Vấn đề: Nhiều mutable borrows cùng lúc
// ❌ Lỗi
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // Error!
println!("{}, {}", r1, r2);
Fix:
// ✅ Dùng tuần tự
let mut s = String::from("hello");
{
let r1 = &mut s;
r1.push_str(" world");
} // r1 dropped
let r2 = &mut s;
r2.push_str("!");
E0597: Borrowed Value Does Not Live Long Enough
Vấn đề: Reference sống lâu hơn data
// ❌ Lỗi
fn bad() -> &String {
let s = String::from("hello");
&s // Error! s dropped khi function kết thúc
}
Fix:
// ✅ Return owned value
fn good() -> String {
let s = String::from("hello");
s // Move ownership
}
// ✅ Hoặc dùng 'static
fn static_ref() -> &'static str {
"hello" // String literal lives forever
}
🖨️ Level 2: println! Debugging
The Classic Approach
ELI5: Như để dấu vết bánh mì - in ra để biết code đi đâu! 🍞
Basic println! Debugging
fn calculate(x: i32, y: i32) -> i32 {
println!("calculate called with x={}, y={}", x, y);
let result = x * 2 + y;
println!("intermediate result: {}", result);
let final_result = result - 5;
println!("final result: {}", final_result);
final_result
}
fn main() {
println!("Program started");
let result = calculate(10, 5);
println!("Got result: {}", result);
println!("Program ended");
}
Output:
Program started
calculate called with x=10, y=5
intermediate result: 25
final result: 20
Got result: 20
Program ended
Debug Print với {:?}
#[derive(Debug)]
struct User {
name: String,
age: u32,
}
fn main() {
let user = User {
name: "Alice".to_string(),
age: 25,
};
// Debug print
println!("User: {:?}", user);
// Output: User: User { name: "Alice", age: 25 }
// Pretty debug print
println!("User: {:#?}", user);
// Output:
// User: User {
// name: "Alice",
// age: 25
// }
}
dbg! Macro - Debug Print Better
Advantages over println!:
- ✅ Prints file, line, expression
- ✅ Returns ownership
- ✅ Prints to stderr (không làm rối stdout)
fn main() {
let x = 5;
let y = 10;
// println! approach
println!("x = {}, y = {}", x, y);
// dbg! approach - better!
dbg!(x);
dbg!(y);
dbg!(x + y);
// Can use in expressions
let z = dbg!(x * 2) + dbg!(y * 3);
dbg!(z);
}
Output:
[src/main.rs:6] x = 5
[src/main.rs:7] y = 10
[src/main.rs:8] x + y = 15
[src/main.rs:11] x * 2 = 10
[src/main.rs:11] y * 3 = 30
[src/main.rs:12] z = 40
Conditional Debug Prints
// Only compile in debug builds
fn calculate(x: i32) -> i32 {
#[cfg(debug_assertions)]
println!("DEBUG: calculate called with x={}", x);
x * 2
}
// Custom debug flag
const DEBUG: bool = true;
fn process(data: &str) {
if DEBUG {
println!("Processing: {}", data);
}
// ... actual processing
}
Debug Print với Timestamps
use std::time::Instant;
fn main() {
let start = Instant::now();
println!("[{:?}] Program started", start.elapsed());
// Some work
std::thread::sleep(std::time::Duration::from_millis(100));
println!("[{:?}] After sleep", start.elapsed());
// More work
let _result = calculate();
println!("[{:?}] After calculate", start.elapsed());
}
fn calculate() -> i32 {
std::thread::sleep(std::time::Duration::from_millis(50));
42
}
Output:
[0ns] Program started
[100.5ms] After sleep
[150.8ms] After calculate
Tracing với log crate
Better cho production code:
# Cargo.toml
[dependencies]
log = "0.4"
env_logger = "0.10"
use log::{debug, error, info, trace, warn};
fn main() {
env_logger::init();
trace!("Very detailed");
debug!("Debug info");
info!("Informational");
warn!("Warning!");
error!("Error occurred!");
}
Run với log level:
RUST_LOG=debug cargo run
RUST_LOG=info cargo run
RUST_LOG=trace cargo run
🔧 Level 3: VS Code Debugger
Setup
1. Install Extensions:
- "rust-analyzer" - Rust language support
- "CodeLLDB" - Debugger
2. Create launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"cargo": {
"args": [
"build",
"--bin=my_program",
"--package=my_package"
],
"filter": {
"name": "my_program",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}