Rust中$($id:ident),*与$($id:ident,)*宏匹配器行为差异原因
Rust宏中
$($id:ident,)*与$($id:ident),*的展开差异及规则解析 核心差异根源:重复匹配的语法结构
Rust macro_rules 的重复匹配语法是 $ ( 重复模式 ) [分隔符] *,两种写法的本质区别在于逗号是属于重复模式的一部分,还是作为重复项之间的分隔符:
$($id:ident,)*:逗号是重复项的后缀
这里的重复模式是$id:ident,(每个标识符后跟逗号),没有额外的分隔符。展开时,每匹配一个标识符就会带上一个逗号:- 匹配0个元素:展开为空,因此
($($id:ident,)*)→() - 匹配1个元素
A:展开为A,,因此($($id:ident,)*)→(A,)(这是Rust中唯一合法的单元素元组写法) - 匹配多个元素
A B:展开为A, B,,因此($($id:ident,)*)→(A, B,)(Rust允许元组末尾带逗号,与(A, B)等价)
- 匹配0个元素:展开为空,因此
$($id:ident),*:逗号是重复项的分隔符
这里的重复模式是$id:ident,分隔符是,。展开时,逗号只会插入在多个重复项之间:- 匹配0个元素:展开为空,因此
($($id:ident),*)→() - 匹配1个元素
A:没有其他项需要分隔,因此展开为A,($($id:ident),*)→(A)(这不是单元素元组,只是括号包裹的普通类型A) - 匹配多个元素
A B:逗号插入在两项之间,展开为A, B,因此($($id:ident),*)→(A, B)
- 匹配0个元素:展开为空,因此
结合示例代码的展开分析
看你提供的factory_tuple宏:
macro_rules! factory_tuple ({ $($param:ident)* } => { impl<Func, Fut, $($param,)*> Handler<($($param,)*)> for Func where Func: Fn($($param),*) -> Fut + Clone + 'static, Fut: Future, { type Output = Fut::Output; type Future = Fut; #[inline] #[allow(non_snake_case)] fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future { (self)($($param,)*) } } });
1. factory_tuple! {}(0个参数)
展开后:
impl<Func, Fut> Handler<()> for Func where Func: Fn() -> Fut + Clone + 'static, Fut: Future, { type Output = Fut::Output; type Future = Fut; #[inline] #[allow(non_snake_case)] fn call(&self, (): ()) -> Self::Future { (self)() } }
2. factory_tuple! { A }(1个参数)
展开后:
impl<Func, Fut, A> Handler<(A,)> for Func where Func: Fn(A) -> Fut + Clone + 'static, Fut: Future, { type Output = Fut::Output; type Future = Fut; #[inline] #[allow(non_snake_case)] fn call(&self, (A,): (A,)) -> Self::Future { (self)(A,) } }
这里Handler<(A,)>正确匹配单元素元组类型,而Fn(A)对应接受单个参数的函数,调用时(self)(A,)与(self)(A)等价,Rust会自动忽略末尾逗号。
3. factory_tuple! { A B }(2个参数)
展开后:
impl<Func, Fut, A, B> Handler<(A, B,)> for Func where Func: Fn(A, B) -> Fut + Clone + 'static, Fut: Future, { type Output = Fut::Output; type Future = Fut; #[inline] #[allow(non_snake_case)] fn call(&self, (A, B,): (A, B,)) -> Self::Future { (self)(A, B,) } }
(A, B,)与(A, B)是等价的元组类型,宏生成的代码完全合法。
关键注意点
在Rust中,(A)和A是同一类型,只有(A,)才是单元素元组类型。这就是为什么处理元组类型时,必须用$($id:ident,)*来保证单元素场景下生成合法的元组语法,而$($id:ident),*更适合用于函数参数列表这类不需要单元素“特殊语法”的场景。
内容的提问来源于stack exchange,提问作者rodman10
相关产品推荐
相关产品推荐

