Skip to content

类函数宏 Function-Lick Macro

所谓的类函数宏就是 使用宏像函数调用函数一样

打印宏

最常用的将内容输出到控制台的宏

作用
print将内容输出到 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!");
}

其他属性宏

这些宏并不常用, 也许一个项目都不一定会用到一次, 可以参考:

Released under the MIT License.