% プッシュダウン蓄積
#![allow(unused)] fn main() { macro_rules! init_array { (@accum (0, $_e:expr) -> ($($body:tt)*)) => {init_array!(@as_expr [$($body)*])}; (@accum (1, $e:expr) -> ($($body:tt)*)) => {init_array!(@accum (0, $e) -> ($($body)* $e,))}; (@accum (2, $e:expr) -> ($($body:tt)*)) => {init_array!(@accum (1, $e) -> ($($body)* $e,))}; (@accum (3, $e:expr) -> ($($body:tt)*)) => {init_array!(@accum (2, $e) -> ($($body)* $e,))}; (@as_expr $e:expr) => {$e}; [$e:expr; $n:tt] => { { let e = $e; init_array!(@accum ($n, e.clone()) -> ()) } }; } let strings: [String; 3] = init_array![String::from("hi!"); 3]; assert_eq!(format!("{:?}", strings), "[\"hi!\", \"hi!\", \"hi!\"]"); }
Rust のすべてのマクロは、式、項目、などの、サポートされている完全な構文要素にならなければなりません。これは、マクロを部分的な構造へ展開することは不可能である、ということを意味します。
上の例は、次のようにもっと直接的に表現できることを期待するかもしれません。
macro_rules! init_array {
(@accum 0, $_e:expr) => {/* 空 */};
(@accum 1, $e:expr) => {$e};
(@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)};
(@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)};
[$e:expr; $n:tt] => {
{
let e = $e;
[init_array!(@accum $n, e)]
}
};
}
期待されるのは、配列リテラルの展開が次のように進むことです。
[init_array!(@accum 3, e)]
[e, init_array!(@accum 2, e)]
[e, e, init_array!(@accum 1, e)]
[e, e, e]
しかし、これには各中間ステップが不完全な式へ展開されることが必要になります。中間結果がマクロコンテキストの外部で使われることが決してないとしても、それは依然として禁止されています。
しかし、プッシュダウンを使うと、完了前のどの時点でも実際に完全な構造を持つ必要なしに、トークン列を段階的に構築できます。冒頭に示した例では、マクロ呼び出しの列は次のように進みます。
init_array! { String:: from ( "hi!" ) ; 3 }
init_array! { @ accum ( 3 , e . clone ( ) ) -> ( ) }
init_array! { @ accum ( 2 , e.clone() ) -> ( e.clone() , ) }
init_array! { @ accum ( 1 , e.clone() ) -> ( e.clone() , e.clone() , ) }
init_array! { @ accum ( 0 , e.clone() ) -> ( e.clone() , e.clone() , e.clone() , ) }
init_array! { @ as_expr [ e.clone() , e.clone() , e.clone() , ] }
ご覧のとおり、各層は蓄積された出力に追加していき、最後に終了規則がそれを完全な構造として出力します。
上記の定式化で唯一重要な部分は、パースを引き起こさずに出力を保持するために $($body:tt)* を使っていることです。($input) -> ($output) の使用は、このようなマクロの振る舞いを明確にしやすくするために採用された単なる慣例です。
プッシュダウン蓄積は、任意に複雑な中間結果を構築できるようにするため、インクリメンタル TT マンチャー の一部として頻繁に使われます。