类函数宏 Function-Lick Macro
所谓的类函数宏就是 使用宏像函数调用函数一样
打印宏
最常用的将内容输出到控制台的宏
| 宏 | 作用 |
|---|---|
| 将内容输出到 stdout (不换行) | |
| println | 将内容输出到 stdout (换行) |
| eprint | 将内容输出到 stderr (不换行) |
| eprintln | 将内容输出到 stderr (换行) |
打印宏占位符及其需要实现的 trait
| 宏占位符 | 需要实现的 trait | 说明 |
|---|---|---|
{} | std::fmt::Display | 基本类型默认实现, 自定义结构体需手动实现 |
{:?} | std::fmt::Debug | 调试输出, 可通过 属性宏自动实现 |
{:x?} | std::fmt::Debug | 十六进制输出(小写) |
{:X?} | std::fmt::Debug | 十六进制输出(大写) |
{:b} | std::fmt::Binary | 二进制输出(bytes) |
{:o} | std::fmt::Octal | 八进制输出 |
{:e} | std::fmt::LowerExp | 科学计数法小写 |
{:E} | std::fmt::UpperExp | 科学计数法大写 |
{:p} | std::fmt::Pointer | 内存地址/指针输出 |
rust
#[derive(Debug)]
struct Person {
id: String,
name: String,
age: u8,
}
fn main() {
//=== 输出静态内容 ===//
print!("Hello"); // 输出 Hello(不换行)
println!(" world!"); // 输出 world!(并且换行)
eprint!("error"); // 将内容输出到 error(不换行)
eprintln!(" message"); // 将内容输出到 stderr(并且换行)
//=== 输出带有变量的内容(基本类型) ===//
let msg = "world";
println!("message: {}!", msg); // 可以使用 {} 占位符
eprintln!("message: {msg}!"); // 也可以直接用 {var} 这种形式
//=== 使用占位符格式化输出(非基本类型) ===//
let p1 = Person {
id: String::from("123456"),
name: String::from("张三"),
age: 18,
};
println!("{:?}", p1);
println!("{p1:?}");
// 输出不换行, 注意修饰符号 :? 必须在{}中, 变量名后
// Person { id: "123456", name: "张三", age: 18 }
println!("{:#?}", p1);
eprintln!("{p1:#?}");
// 格式化输出, 注意修饰符号 :#? 必须在{}中, 变量名后
// Person {
// id: "123456",
// name: "张三",
// age: 18,
// }
}断言宏
断言宏, 常用语单元测试, 硬要用于代码中也是可以的, 只是传入的值不符合期望就会 panic
| 宏 | 作用 |
|---|---|
| assert | 断言传入的参数是否为真 |
| assert_eq | 断言传入的两个参数是否相等 |
| assert_ne | 断言传入的两个参数是否不相等 |
rust
fn main() {
let x = 1;
assert!(x == 1); // 直接断言, 如果不等1就会 panic
// assert!(x == 2, "x is not 2");
// 断言并自定义 panic 消息
// thread 'main' (37139872) panicked at src/main.rs:4:5:
// x is not 2
assert_eq!(x, 1); // 断言, 传入两个值必须相等, 否则 panic
assert_ne!(x, 2); // 断言, 传入两个值必须不相等, 否则 panic
}创建数据/格式化宏
| 宏 | 作用 |
|---|---|
| format | 将传入的内容按照格式化返回为字符串 |
| stringify | 将传入的内容原样返回为字符串 |
| include_str | 读取文件内容并返回字符串 |
| include_bytes | 读取文件内容并返回二进制数据流 |
| vec | 创建 Vec |
rust
fn main() {
// 1.将传入的内容按照格式化返回为字符串
let string0 = format!("{}-{}-{}", 2026, "06", "01");
println!("{string0}"); // 2026-06-01
let string1 = format!("{}-{}-{}", "2026", "06", "01");
println!("{string1}"); // 2026-06-01
// 2.将传入的内容, 原样输出为字符串
let string2 = stringify!(2026 - 07 - 02);
println!("{string2}"); // 2026-07-02
let string3 = stringify!({ 2026 - 08 - 03 });
println!("{string3}"); // {2026-08-03}
let string4 = stringify!("2026-09-04");
println!("{string4}"); // "2026-09-04"
// 3.从文件中读取内容为字符串
let string5 = include_str!("./main.rs");
println!("{string5}");
// 4.从文件中读取内容为二进制数据流(bytes)
let bytes = include_bytes!("./main.rs");
println!("bytes len: {}", bytes.len());
// 5.创建 Vec
let nums = vec![1, 2, 3];
println!("{nums:?}"); // [1, 2, 3]
}编译时信息宏
用于获取源码在编译器中的信息
| 宏 | 作用 |
|---|---|
| file | 宏所在的源码文件路径 |
| line | 宏所在的源码文件行数 |
| coloumn | 宏所在的源码文件列数 |
| module_path | 宏所在的源码文件的模块 |
| env | 获取系统环境变量 |
| cfg | 与 #[cfg] 搭配使用 |
rust
fn main() {
// 编译时信息
println!("{}", file!()); // 当前源码文件路径 (src/main.rs)
println!("{}", line!()); // 当前行号 (4)
println!("{}", column!()); // 当前列号 (20)
println!("{}", module_path!()); // 当前模块路径 (rust_demo)
println!("{}", env!("CARGO_PKG_VERSION")); // 环境变量(Cargo.toml 中的version 字段) 0.0.1
println!("{}", env!("HOME")); // 环境变量(家目录) /Users/jerry
// cfg!
let dir = if cfg!(windows) {
"windows-specific-directory"
} else {
"unix-directory"
};
}代码标记宏
| 宏 | 作用 |
|---|---|
| panic | 程序立即终止并向程序的调用者提供反馈 |
| todo | 标记某个函数未实现(没有标记编译器会报错) |
| unimplemented | 与 ToDo 功能类似(如果没有实现, 在执行时会 panic) |
rust
fn test_panic() {
panic!("error message from test_panic");
}
fn test_todo() -> String {
todo!(); // 如果没有这个宏, 不返回任何东西编译器会报错
}
fn test_unimplemented() -> String {
unimplemented!(); // 如果没有这个宏, 不返回任何东西编译器会报错
}
fn main() {
// let str = test_unimplemented();
// println!("{str}");
// thread 'main' (37213635) panicked at src/main.rs:10:5:
// not implemented
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
// let str = test_todo();
// println!("{str}");
// thread 'main' (37210935) panicked at src/main.rs:6:5:
// not yet implemented
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
test_panic();
// thread 'main' (37206114) panicked at src/main.rs:2:5:
// error message from panic_f1
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
}类属性宏 Attribute-Like Macro
派生属性宏 derive
derive 可以自动给某个 struct 实现一个或多个 Trait, 如: #[derive(Debug, Clone)]
注意点
派生宏只能用于修饰 struct(结构体) 或者 enum(枚举)
rust
// 使用派生宏自动实现 std::fmt::Debug trait
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
struct Animal {
name: String,
age: u8,
}
impl std::fmt::Display for Animal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write! 表示将字符串格式化,并将结果传递到 writer
// https://rustwiki.org/zh-CN/std/macro.write.html
write!(f, "name={}, age={}", self.name, self.age)
}
}
fn main() {
let p = Person {
name: "张三".to_string(),
age: 18,
};
// 注意 Person struct 没有实现 Display trait
// 所以不能使用 {} 占位符来输出, 而之所以能够
// 使用 {:?} 占位符输出, 说明 #[derive(Debug)]
// 自动实现了 Debug trait
// println!("{p}"); // 编译器报错
println!("{p:?}");
let a = Animal {
name: "小狗".to_string(),
age: 3,
};
// 注意 Animal struct 没有实现 Debug trait
// 所以不能用 {:?} 占位符来输出
// 但是, 我们手动实现了 std::fmt::Display trait
// 所以, 能够直接使用 {} 占位符来输出
// println!("{a:?}");
println!("{a}");
}测试属性宏
测试宏是专门用于修饰单元测试的内容的宏
#[cfg(test)]#[test]#[should_panic]#[ignore]
rust
fn main() {
let x = sum(vec![1, 2, 3]);
println!("sum: {}", x);
pan();
}
fn sum(nums: Vec<usize>) -> usize {
nums.iter().sum()
}
fn pan() {
panic!("error")
}
// 将一个模块标记为单元测试模块
#[cfg(test)]
pub mod unit_test_mod {
use super::*;
// 将一个模块标记为单元测试例子
#[test]
fn test_sum() {
assert_eq!(sum(vec![1, 2, 3]), 6);
}
// 这个函数必须要 panic 否则测试不通过
#[should_panic]
#[test]
fn test_pan() {
pan();
}
// 在运行 cargo test 时, 忽略这个函数
#[ignore]
fn test_ignore() {
// 这个函数将不会被执行, 所以不通过也没关系
assert_eq!(1, 2);
}
}诊断属性宏
诊断属性宏用于设置某些代码检查器的行为: 比如一些没有用到的代码, 默认情况下会报错
#[allow(rule)]#[warn(rule)]#[deny(rule)]
这些代码检查器规则可以去这里找到
rust
// 如果不加 #[allow(unused)] 这个宏
// 直接执行, 编译器会报警告
// warning: struct `Person` is never constructed
// --> src/main.rs:5:8
// |
// 5 | struct Person {
// | ^^^^^^
// |
// = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
#[allow(unused)]
struct Person {
name: String,
}
fn main() {
println!("Hello, world!");
}其他属性宏
这些宏并不常用, 也许一个项目都不一定会用到一次, 可以参考: