在Rust声明式宏中,能否从字符串/整数构造ident?
问题描述
我有十几个crate,每个都包含part1()和part2()函数,想要打印每个crate中这两个函数的返回值。为避免重复代码,我写了如下声明式宏,主crate的main()函数实现如下:
fn main() { macro_rules! print_day { ($day:ident) => { let day = stringify!($day).replace('d', ""); println!("Day {day} Part 1: {}", $day::part1()); println!("Day {day} Part 2: {}", $day::part2()); }; } print_day!(d01); print_day!(d02); print_day!(d03); // 后续还有更多类似调用.. }
现在我想改成通过循环调用该宏,传入整数并转换为对应的ident(比如数字1转为d01),期望实现的逻辑如下:
fn main() { macro_rules! print_day { ($day:expr) => { let daystr = format!("{:02}", $day); let dayident = ???; println!("Day {daystr} Part 1: {}", $dayident::part1()); println!("Day {daystr} Part 2: {}", $dayident::part2()); }; } for day in 0..=num_days { print_day!(day); } }
但目前找到的方案都涉及过程宏或额外依赖,请问能否在声明式宏中直接从字符串或整数构造ident实现该需求?
解答
核心结论
声明式宏无法实现从字符串/动态整数构造ident的需求,原因有两点:
- 声明式宏是编译期展开的,而循环中的
day是运行时动态变量,编译期宏无法获取运行时的值; - Rust声明式宏的语法本身不支持将字符串或动态数值转换为标识符,标识符必须是宏展开阶段就能确定的静态符号。
无额外依赖的替代方案
不用过程宏或第三方库,依然可以通过声明式宏批量生成调用,避免手动重复编写代码:
方案1:批量列举标识符
新增一层宏,一次性传入所有day的标识符,自动生成所有print_day调用:
fn main() { macro_rules! print_day { ($day:ident) => { let day = stringify!($day).replace('d', ""); println!("Day {day} Part 1: {}", $day::part1()); println!("Day {day} Part 2: {}", $day::part2()); }; } macro_rules! print_all_days { ($($day:ident),*) => { $(print_day!($day);)* }; } // 传入所有需要调用的day标识符,按需扩展 print_all_days!(d01, d02, d03, d04, d05); }
方案2:递归宏生成连续标识符
如果你的day是连续编号的,可以用标准库的concat_idents!宏拼接标识符,配合递归宏自动生成连续的调用:
fn main() { macro_rules! print_day { ($day:ident) => { let day = stringify!($day).replace('d', ""); println!("Day {day} Part 1: {}", $day::part1()); println!("Day {day} Part 2: {}", $day::part2()); }; } macro_rules! print_days_recursive { // 递归终止条件:无剩余天数 () => {}; // 递归处理当前天数,再处理剩余天数 ($current:expr, $($rest:expr),*) => { print_day!(concat_idents!(d, $current)); print_days_recursive!($($rest),*); }; } // 传入连续的编号(带前导零),自动生成d01到d05的调用 print_days_recursive!(01, 02, 03, 04, 05); }
concat_idents!是Rust标准库提供的宏,仅能在编译期拼接静态标识符片段,正好适配连续编号的场景。
内容的提问来源于stack exchange,提问作者Diktomat
相关产品推荐
相关产品推荐

