问题
I have two sets of incomplete types (i.e. struct names, missing generic parameters and lifetimes), and I need to have some code executed for each possible pair of combinations:
// these are my types
struct A<T> { ... }
struct B<'a, 'b, T> { ... }
struct C { ... }
struct X<T> { ... }
struct Y { ... }
struct W<'a> { ... }
struct Z<T, D> { ... }
// this is the code I need to generate
match (first_key, second_key) {
("a", "x") => { ... A ... X ... }
("a", "y") => { ... A ... Y ... }
("a", "w") => { ... A ... W ... }
("a", "z") => { ... A ... Z ... }
("b", "x") => { ... B ... X ... }
("b", "y") => { ... B ... Y ... }
// ...
}
The structures of the first set (A
, B
, C
) and the ones on the second set (X
, Y
, W
, Z
) have a generic parameter depending on each other (e.g. for the case ("a", "x")
, the actual types that will be used are A<X>
and X< A<X>::Color >
). For this reason I couldn't find any solution using generic functions or similar.
I believe that the problem could be easily solvable with a macro; something like:
macro_rules! my_code {
( $first_type:tt), $second_type:tt ) => {
// ... $first_type ... $second_type ...
}
}
product_match!( (first_key, second_key) {
{ "a" => A, "b" => B, "c" => C },
{ "x" => X, "y" => Y, "w" => W, "z" => Z }
} => my_code )
but I failed at implementing product_match
after working for several hours on it already. I couldn't find any easy way to nest repetitions; I believe that the only solution is using macros to turn the lists of match cases into nested tuples of values, and then recurse over them, but I found this very difficult to implement.
Another option could be generating the code of that big match
using a build script, but this solution sounds quite dirty.
Is there any easy solution to this problem that I missed? Is there any easy way to implement product_match!
? How can I implement my logic?
回答1:
I think your idea of implementing a Cartesian product using macros is best.
I'm not quite sure what you want the match
expression to be, so I've implemented a repeated function call instead. The macro plumbing should be much the same, however. Hopefully you can take it from here.
macro_rules! cp_inner {
($f: expr, $x: expr, [$($y: expr),*]) => {
$($f($x, $y);)*
}
}
macro_rules! cartesian_product {
($f: expr, [$($x: expr),*], $ys: tt) => {
$(cp_inner!($f, $x, $ys);)*;
}
}
fn print_pair(x: u32, y: &'static str) {
println!("({}, {})", x, y);
}
pub fn main() {
cartesian_product!(print_pair, [1, 2, 3], ["apple", "banana", "cherry"]);
}
回答2:
Here is a macro cartesian_match
that can be used as follows:
fn main() {
macro_rules! test( ($x: tt, $y: tt, $z: tt,) => {
println!("{:?} {:?} {:?}", $x, $y, $z);
});
#[derive(Debug)]
enum E {
V1, V2, V3,
}
let b = false;
let oi = Some(6);
let e = E::V1;
cartesian_match!(
test,
match (oi) {
Some(n) => {format!("{} is the number", n)},
None => {None as Option<usize>},
},
match (b) {
true => true,
false => {E::V3},
},
match (e) {
E::V1 => true,
E::V2 => 'l',
E::V3 => 2,
},
);
}
Calling cartesian_match
is a bit rough around the edges (note all the braces), and possibly does not support all the patterns supported in ordinary match
statements.
The macro is defined as follows:
macro_rules! cartesian_match(
(
$macro_callback: ident,
$(match ($e: expr) {
$($x: pat => $y: tt,)*
},)*
) => {
cartesian_match!(@p0,
$macro_callback,
(),
$(match ($e) {
$($x => $y,)*
},)*
)
};
(@p0,
$macro_callback: ident,
$rest_packed: tt,
match ($e: expr) {
$($x: pat => $y: tt,)*
},
$(match ($e2: expr) {
$($x2: pat => $y2: tt,)*
},)*
) => {
cartesian_match!(@p0,
$macro_callback,
(
match ($e) {
$($x => $y,)*
},
$rest_packed,
),
$(match ($e2) {
$($x2 => $y2,)*
},)*
)
};
(@p0,
$macro_callback: ident,
$rest_packed: tt,
) => {
cartesian_match!(@p1,
$macro_callback,
@matched{()},
$rest_packed,
)
};
(@p1,
$macro_callback: ident,
@matched{$matched_packed: tt},
(
match ($e: expr) {
$($x: pat => $y: tt,)*
},
$rest_packed: tt,
),
) => {
match $e {
$($x => cartesian_match!(@p1,
$macro_callback,
@matched{ ($matched_packed, $y,) },
$rest_packed,
),)*
}
};
(@p1,
$macro_callback: ident,
@matched{$matched_packed: tt},
(),
) => {
cartesian_match!(@p2,
$macro_callback,
@unpacked(),
$matched_packed,
)
//$macro_callback!($matched_packed)
};
(@p2,
$macro_callback: ident,
@unpacked($($u: tt,)*),
(
$rest_packed: tt,
$y: tt,
),
) => {
cartesian_match!(@p2,
$macro_callback,
@unpacked($($u,)* $y,),
$rest_packed,
)
};
(@p2,
$macro_callback: ident,
@unpacked($($u: tt,)*),
(),
) => {
$macro_callback!($($u,)*)
};
);
It takes a variable number of match
items and expands them in a nested manner one after another. It does so in different "internal phases" (denoted by the @
-prefixed parameter in the macro argument lists):
- Phase
@p0
takes a list ofmatch
es and converts them into a singlett
. Essentially, it transformsmatch_1, match_2, match_3,
into something like(match_1, (match_2, (match_3, (),)))
. (This is done to prevent "inconsistent lockstep iteration".) - Phase
@p1
unpacks the thing generated by@p0
and converts it into nestedmatch
statements. It uses the same trick as@p0
to store the elements matched up to the current nesting depth. - Phase
@p2
unpacks the thing generated by@p1
(i.e. it essentially converts((((), v3), v2), v1,)
tov1, v2, v3
and passes it into the specified callback.
来源:https://stackoverflow.com/questions/42767999/cartesian-product-match