embassy中有个宏,check_at_most_one,这个宏通过生成编译时条件编译指令来检查用户是否只配置了一个编译条件。具体来说,它会生成一系列的 #[cfg]
属性,这些属性会在编译时进行检查。如果发现有多个 feature 同时启用,就会触发编译错误。
macro_rules! check_at_most_one {
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
#[cfg(any($($res)*))]
compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*));
};
(@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => {
check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]);
};
($($f:literal),*$(,)?) => {
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
};
}
check_at_most_one!(
"arch-avr",
"arch-cortex-m",
"arch-riscv32",
"arch-std",
"arch-wasm",
"arch-spin",
);
以上面的使用例子来分析,check_at_most_one! 第一次调用时会走第三个分支,
($($f:literal),*$(,)?) => {
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
};
这个分支比较简单,前面添加@amo,是为了迭代调用时进入另外的分支,接着将传入的参数生成两个一样的数组,第一个数组在后续循环中会保持不变,第二个数组用来生成所有all()组合用的,第三个空数组是用来存储输出数据用的,调用展开为:
check_at_most_one!(@amo ["arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm", "arch-spin"] ["arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm", "arch-spin"] []);
再次轮询时会走第二分支,进入递归处理流程
(@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => {
check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]);
};
这个有点复杂,一个个分析
$feats:tt 这个会取得第一个数组的全部值,后续递归也一直保持不变
[$curr:literal $($rest:literal)*] 每次递归时,从第二个数组中,$curr:literal 取出第一个项,$($rest:literal)*为后续剩余所有的项,
[$($res:tt)*]为第三个数组展开,第一次迭代为空数组
第一次调用展开为
check_at_most_one!(@amo ["arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm", "arch-spin"]
["arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm", "arch-spin"]
[all(feature="arch-avr", feature="arch-cortex-m"),
all(feature="arch-avr", feature="arch-riscv32"),
all(feature="arch-avr", feature="arch-std"),
all(feature="arch-avr", feature="arch-wasm"),
all(feature="arch-avr", feature="arch-spin"),]);
第二次展开为:
check_at_most_one!(@amo ["arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm", "arch-spin"]
["arch-riscv32", "arch-std", "arch-wasm", "arch-spin"]
[all(feature="arch-avr", feature="arch-cortex-m"),
all(feature="arch-avr", feature="arch-riscv32"),
all(feature="arch-avr", feature="arch-std"),
all(feature="arch-avr", feature="arch-wasm"),
all(feature="arch-avr", feature="arch-spin"),
//以下为第二次迭代新添加的项
all(feature="arch-cortex-m", feature="arch-riscv32"),
all(feature="arch-cortex-m", feature="arch-std"),
all(feature="arch-cortex-m", feature="arch-wasm"),
all(feature="arch-cortex-m", feature="arch-spin"),]);
以此类推,当迭代到第二个数组为空时,就会进入第一个分支:
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
#[cfg(any($($res)*))]
compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*));
};
这里展开相当于是:
#[cfg(any(
all(feature="arch-avr", feature="arch-cortex-m"),
all(feature="arch-avr", feature="arch-riscv32"),
all(feature="arch-cortex-m", feature="arch-riscv32"),
// ... 其他组合
))]
compile_error!("At most one of these features can be enabled at the same time: `arch-avr` `arch-cortex-m` `arch-riscv32` `arch-std` `arch-wasm` `arch-spin`");
这样就意味着,如果这个组合里面,用户同时打开多个feature,编译就会报错。保证用户只能打开其中一个feature,这个宏的设计方式挺精妙。可以在有多个互斥feature的场景下拿来直接使用。