以下是关于 Rust 编程语言中 print 函数(实际为宏)和 debug 宏的教程。 Rust 不提供内置的 print 函数,而是使用宏(如 print! 和 println!)来处理输出。这些宏是标准库的一部分,用于控制台打印。调试宏如 dbg! 用于开发时快速检查值,而 Debug trait 则用于结构化数据的打印。
1. print! 和 println! 宏:基本输出
Rust 使用宏来处理格式化输出,因为宏允许在编译时扩展代码,提供灵活性。print! 用于打印不换行,println! 用于打印并换行。它们类似于 C 的 printf,但使用 Rust 的格式化语法。
-
语法:
print!("格式字符串", 参数...);
:打印到标准输出(stdout),不添加换行。println!("格式字符串", 参数...);
:打印并添加换行。- 格式字符串使用
{}
作为占位符,参数会自动推断类型。 - 支持命名参数
{name}
和位置参数{0}
。
-
基本示例:
fn main() { print!("Hello, "); println!("world!"); // 输出: Hello, world!(换行) let x = 42; println!("x 的值为: {}", x); // 输出: x 的值为: 42 println!("{} + {} = {}", 1, 2, 1 + 2); // 输出: 1 + 2 = 3 }
-
格式化选项:
{:?}
:调试格式(用于 Debug trait)。{:#?}
:美化调试格式(多行缩进)。{:b}
:二进制,{:x}
:十六进制,{:o}
:八进制。{:.2}
:浮点数精度(小数点后两位)。 示例:
fn main() { let pi = 3.141592; println!("Pi 约为 {:.2}", pi); // 输出: Pi 约为 3.14 println!("二进制: {:b}", 10); // 输出: 二进制: 1010 }
-
命名参数: 示例:
fn main() { println!("{subject} {verb} {object}", object="懒狗", subject="快速的棕色狐狸", verb="跳过"); // 输出: 快速的棕色狐狸 跳过 懒狗 }
-
注意:这些宏会 panic 如果格式字符串无效(如参数不匹配)。输出到 stderr 使用 eprint! 和 eprintln!。
2. debug 宏:dbg!
dbg! 宏用于调试,它打印表达式的值和源代码位置,然后返回该值。适合插入代码中快速检查,而不中断流程。
-
语法:
dbg!(表达式);
:打印表达式的文件名、行号、列号和值,返回表达式本身。- 支持借用(&),避免所有权转移。
-
示例:
fn main() { let x = 5; let y = dbg!(x * 2); // 打印: [src/main.rs:3:13] x * 2 = 10,返回 10 dbg!(y + 1); // 打印: [src/main.rs:4:5] y + 1 = 11 }
-
与结构体结合: dbg! 使用 Debug trait,如果结构体未实现 Debug,会编译错误。
-
注意:dbg! 只在调试构建中有效,在发布模式下可能被优化掉。输出到 stderr,便于区分正常输出。
3. Debug trait:结构化调试打印
Debug trait 用于自定义类型的调试输出,常与 {:?}
或 {:#?}
结合。标准库类型(如 i32、String)已实现 Debug,自定义类型需派生或手动实现。
-
派生 Debug: 使用
#[derive(Debug)]
注解结构体或枚举,自动生成 Debug 实现。 示例:#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect = Rectangle { width: 30, height: 50 }; println!("rect 是 {:?}", rect); // 输出: rect 是 Rectangle { width: 30, height: 50 } println!("rect 是 {:#?}", rect); // 美化输出,多行缩进 }
-
手动实现 Debug: 如果需要自定义格式,实现
std::fmt::Debug
trait。 示例:use std::fmt; struct Point { x: i32, y: i32, } impl fmt::Debug for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Point") .field("x", &self.x) .field("y", &self.y) .finish() } } fn main() { let p = Point { x: 1, y: 2 }; println!("{:?}", p); // 输出: Point { x: 1, y: 2 } }
-
Debug vs Display:
- Debug:用于开发者,格式如
{ x: 1, y: 2 }
,通过{:?}
。 - Display:用于用户友好输出,通过
{}
。需手动实现std::fmt::Display
。 示例(Display):
#![allow(unused)] fn main() { impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } println!("{}", p); // 输出: (1, 2) }
- Debug:用于开发者,格式如
注意事项
- 性能:打印宏在发布模式下高效,但 dbg! 适合开发阶段。
- 所有权:dbg! 借用值,避免移动;println! 等宏不消耗所有权。
- 错误处理:打印到 stdout/stderr 是阻塞的,如果 IO 失败会 panic。
- 替代:对于复杂日志,使用 log 或 tracing crate。
- 实践:在 RustRover 或命令行中使用
cargo run
测试这些示例,观察输出差异。