欢迎光临
我们一直在努力

【Rust入门知识点学与练】第22课:宏 Macros

知识点:声明式宏 macro_rules!

声明式宏基于模式匹配,类似 match 但作用于代码:

// 最简单的宏
macro_rules! say_hello {
() => {
println!("Hello!");
};
}

// 带参数的宏
macro_rules! create_function {
(name:ident) => {
fn name() {
println!("函数 {:?} 被调用", stringify!(name));
}
};
}

create_function!(foo);
create_function!(bar);

// 重复模式:用 (…)* 或 (…),+ 匹配多个
macro_rules! sum {
// 接受任意数量的表达式,用逗号分隔
((x:expr),*) => {
{
let mut total = 0;
(
total += x; // 对每个 x 展开
)*
total
}
};
}

// 带分隔符的重复
macro_rules! vec_of_strings {
((s:expr),*) => {
{
let mut v = Vec::new();
(
v.push(String::from(s));
)*
v
}
};
}

fn main() {
say_hello!(); // Hello!

foo(); // 函数 "foo" 被调用
bar(); // 函数 "bar" 被调用

println!("总和: {}", sum!(1, 2, 3, 4, 5)); // 15
println!("空和: {}", sum!()); // 0

let strings = vec_of_strings!("a", "b", "c");
println!("字符串: {:?}", strings); // ["a", "b", "c"]
}

知识点:宏的模式匹配片段说明符

说明符 匹配内容 示例
ident 标识符 foo, my_var
expr 表达式 1+2, x, foo()
ty 类型 i32, String
tt 单个 token tree 几乎任何东西
pat 模式 Some(x), _
stmt 语句 let x = 1;
block 代码块 { … }
path 路径 std::io::Result
meta 元数据 derive(Debug)
lifetime 生命周期 'a

// 多种片段说明符
macro_rules! create_struct {
(name:ident, (field:ident : ty:ty),*) => {
#[derive(Debug)]
struct name {
(field: ty),*
}
};
}

create_struct!(Person, name: String, age: u32);

macro_rules! match_value {
(val:expr, (pat:pat => result:expr),*) => {
match val {
(pat => result,)*
_ => println!("没有匹配"),
}
};
}

fn main() {
let p = Person { name: String::from("张三"), age: 25 };
println!("{:?}", p);

match_value!(3,
1 => println!("一"),
2 => println!("二"),
3 => println!("三")
);
}

知识点:重复模式中的分隔符

// (…),+ 表示至少一个,逗号分隔
// (…),* 表示零个或多个,逗号分隔
// (…);+ 表示至少一个,分号分隔

// 类似 println! 的宏
macro_rules! my_println {
// 无参数
() => {
print!("n");
};
// 只有格式字符串
(fmt:literal) => {
print!(concat!(fmt, "n"));
};
// 格式字符串 + 参数
(fmt:literal, (arg:expr),*) => {
print!(concat!(fmt, "n"), (arg),*);
};
}

// 键值对宏
macro_rules! hashmap {
((key:expr => value:expr),* (,)?) => {
{
let mut map = std::collections::HashMap::new();
(
map.insert(key, value);
)*
map
}
};
}

fn main() {
my_println!();
my_println!("hello");
my_println!("{} + {} = {}", 1, 2, 3);

let map = hashmap! {
"a" => 1,
"b" => 2,
"c" => 3,
};
println!("{:?}", map);
}

知识点:过程宏概述

过程宏接收代码作为输入,处理后返回代码。需要在单独的 crate 中定义(Cargo.toml 中设置 proc-macro = true)。

// 过程宏有三种形式:

// 1. derive 宏:自动实现 trait
// 用法:#[derive(MyTrait)]
// 在 proc-macro crate 中定义:
//
// #[proc_macro_derive(Hello)]
// pub fn hello_derive(input: TokenStream) -> TokenStream {
// // input 是标注了 #[derive(Hello)] 的类型定义
// // 返回要追加的代码
// }

// 2. 属性宏:自定义属性
// 用法:#[my_attribute]
//
// #[proc_macro_attribute]
// pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
// // args 是属性参数,input 是被标注的项
// }

// 3. 函数式宏:类似函数调用
// 用法:my_macro!(…)
//
// #[proc_macro]
// pub fn my_macro(input: TokenStream) -> TokenStream {
// // input 是宏调用中的内容
// }

知识点:derive 宏示例

// 假设在 proc-macro crate 中定义了 Hello derive 宏:
//
// #[proc_macro_derive(Hello)]
// pub fn hello_derive(input: TokenStream) -> TokenStream {
// let ast = syn::parse(input).unwrap();
// impl_hello(&ast)
// }
//
// fn impl_hello(ast: &DeriveInput) -> TokenStream {
// let name = &ast.ident;
// let gen = quote! {
// impl Hello for #name {
// fn hello(&self) {
// println!("Hello, {}!", stringify!(#name));
// }
// }
// };
// gen.into()
// }

// 在使用侧:
// use my_macro_crate::Hello;
//
// trait Hello {
// fn hello(&self);
// }
//
// #[derive(Hello)]
// struct World;
//
// fn main() {
// let w = World;
// w.hello(); // Hello, World!
// }

知识点:实用内置宏

fn main() {
// vec! — 创建向量
let v = vec![1, 2, 3];
let v2 = vec![0; 5]; // [0, 0, 0, 0, 0]

// println! / print! — 格式化输出
println!("{} + {} = {}", 1, 2, 3);

// format! — 格式化字符串(不打印)
let s = format!("Hello, {}!", "world");
println!("{}", s);

// assert! / assert_eq! / assert_ne! — 断言
assert!(1 + 1 == 2);
assert_eq!("hello", "hello");
assert_ne!(1, 2);

// panic! — 触发 panic
// panic!("出错了!");

// unreachable! — 标记不可达代码
// match 中某些分支不可能到达时
let x: Option<i32> = Some(1);
let val = match x {
Some(v) => v,
None => unreachable!("不应该到这里"),
};
println!("{}", val);

// todo! / unimplemented! — 标记待实现
// fn not_done() -> i32 {
// todo!("还没实现")
// }

// env! — 编译时读取环境变量
// let home = env!("HOME");

// include! / include_str! / include_bytes! — 编译时包含文件
// let content = include_str!("other_file.txt");

// cfg! — 编译时条件判断
if cfg!(target_os = "linux") {
println!("运行在 Linux 上");
} else if cfg!(target_os = "windows") {
println!("运行在 Windows 上");
}
}

核心规则

概念 写法
声明宏 macro_rules! name { pattern => expansion; }
匹配零或多个 (…)*
匹配一个或多个 (…)+
分隔符 (…),* 逗号分隔
片段说明符 name:ident, name:expr, name:ty
字符串化 stringify!(name)
拼接字符串 concat!(a, $b)
derive 宏 #[proc_macro_derive(Name)]
属性宏 #[proc_macro_attribute]
函数式宏 #[proc_macro]

动手试试

补全下面的代码:

// 补全:定义宏 my_vec!,功能类似标准库的 vec!
// 支持两种用法:
// 1. my_vec![1, 2, 3] => vec![1, 2, 3]
// 2. my_vec![0; 5] => vec![0, 0, 0, 0, 0]
// 提示:用两个匹配分支

// 补全:定义宏 my_assert_eq!,功能类似 assert_eq!
// 接受两个表达式,如果不相等则 panic 并显示两边的值和位置
// 期望输出格式(当 a=1, b=2 时):
// "断言失败: a != b, 左边 = 1, 右边 = 2"

// 补全:定义宏 count!,统计传入的参数个数
// count!() => 0
// count!(1) => 1
// count!(1, 2, 3) => 3
// 提示:用递归或重复模式

// 补全:定义宏 impl_display_for_enum!
// 接受一个枚举名和所有变体名
// 自动为该枚举实现 Display trait
// 每个变体的显示文本就是变体名本身
// 例如:
// enum Color { Red, Green, Blue }
// impl_display_for_enum!(Color, Red, Green, Blue);
// 则 Color::Red.to_string() == "Red"

fn main() {
// 测试 my_vec!
let v1 = my_vec![10, 20, 30];
println!("v1: {:?}", v1);
// 期望: [10, 20, 30]

let v2 = my_vec![7; 4];
println!("v2: {:?}", v2);
// 期望: [7, 7, 7, 7]

// 测试 my_assert_eq!
my_assert_eq!(1 + 1, 2);
println!("断言通过: 1+1 == 2");

// 测试 count!
println!("count!(): {}", count!());
println!("count!(1): {}", count!(1));
println!("count!(1,2,3): {}", count!(1, 2, 3));
// 期望: 0, 1, 3

// 测试 impl_display_for_enum!
#[derive(Debug)]
enum Color {
Red,
Green,
Blue,
}

impl_display_for_enum!(Color, Red, Green, Blue);

println!("{}", Color::Red); // Red
println!("{}", Color::Green); // Green
println!("{}", Color::Blue); // Blue
}

期望输出:
v1: [10, 20, 30]
v2: [7, 7, 7, 7]
断言通过: 1+1 == 2
count!(): 0
count!(1): 1
count!(1,2,3): 3
Red
Green
Blue

写好了贴过来,没问题就进入第23课:综合项目实战 👋

贴代码

下面是完整可运行答案,四个Rust声明宏(macro_rules!):my_vec、my_assert_eq、count、impl_display_for_enum。

use std::fmt;

// 1. my_vec! 模仿标准库vec!,两种模式:逗号分隔 / 值;数量
macro_rules! my_vec {
($($elem:expr),+ $(,)?) => {{
let mut v = Vec::new();
$(v.push($elem);)+
v
}};
($val:expr ; $n:expr) => {{
vec![$val; $n]
}};
}

// 2. my_assert_eq!,两个表达式,失败panic,打印左右值
macro_rules! my_assert_eq {
($a:expr, $b:expr) => {{
if $a != $b {
panic!("断言失败: a != b, 左边 = {:?}, 右边 = {:?}", $a, $b);
}
}};
}

// 3. count! 统计可变参数个数,重复模式
macro_rules! count {
() => (0);
($first:expr $(, $rest:expr)*) => (1 + count!($($rest),*));
}

// 4. impl_display_for_enum! 自动给枚举实现Display,变体名称作为输出字符串
macro_rules! impl_display_for_enum {
($enum_name:ident, $($variant:ident),+ $(,)?) => {
impl fmt::Display for $enum_name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
$(
$enum_name::$variant => write!(f, stringify!($variant)),
)+
}
}
}
};
}

fn main() {
// 测试 my_vec!
let v1 = my_vec![10, 20, 30];
println!("v1: {:?}", v1);
// 期望: [10, 20, 30]

let v2 = my_vec![7; 4];
println!("v2: {:?}", v2);
// 期望: [7, 7, 7, 7]

// 测试 my_assert_eq!
my_assert_eq!(1 + 1, 2);
println!("断言通过: 1+1 == 2");

// 测试 count!
println!("count!(): {}", count!());
println!("count!(1): {}", count!(1));
println!("count!(1,2,3): {}", count!(1, 2, 3));
// 期望: 0, 1, 3

// 测试 impl_display_for_enum!
#[derive(Debug)]
enum Color {
Red,
Green,
Blue,
}

impl_display_for_enum!(Color, Red, Green, Blue);

println!("{}", Color::Red); // Red
println!("{}", Color::Green); // Green
println!("{}", Color::Blue); // Blue
}

运行输出

plaintext

v1: [10, 20, 30]
v2: [7, 7, 7, 7]
断言通过: 1+1 == 2
count!(): 0
count!(1): 1
count!(1,2,3): 3
Red
Green
Blue

宏知识点逐条解析

1)my_vec!

两个分支:

  • (((elem:expr),+ :一个或多个表达式,逗号分隔,循环push;尾部可选逗号 $(,)? 兼容 my_vec![1,2,3,]
  • $val:expr ; $n:expr :重复模式,直接调用标准vec!实现(练习场景)。
  • 注意:真正标准库vec!的 x;N 要求x实现Clone;这里直接复用vec!不用自己实现。

    2)my_assert_eq!

    捕获两个表达式a、b;一定要用{ }包裹宏体,形成语句块。
    条件不相等调用panic, {:?} 打印两边的值(支持任意Debug类型)。

    标准assert_eq还会附带文件名、行号;本题没有要求行号就简化实现。

    3)count!(递归宏)

    count!() => 0;
    count!(first , rest…) => 1 + count!(rest…)

    递归计数,编译期算出常量个数,运行时无开销。

    4)impl_display_for_enum!(宏生成impl代码)

    • ident :标识符(枚举名、变体名);
    • stringify!($variant) :把标识符转成字符串;
    • $( … )+:对全部变体批量生成match分支。

    **调用: **
    impl_display_for_enum!(Color,Red,Green,Blue);
    宏展开之后就是完整的 impl fmt::Display for Color。

    Rust学习阶段小结

    到这里:Rust基础、所有权、生命周期、泛型、trait、关联类型、闭包、迭代器、Rc‑RefCell、Arc‑Mutex、多线程、Result/自定义错误、矩阵trait样例、声明宏 macro_rules! 全部通关。

    赞(0)
    未经允许不得转载:171主机测评 » 【Rust入门知识点学与练】第22课:宏 Macros
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址