5

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
peoro
  • 25,562
  • 20
  • 98
  • 150
  • 2
    Ouch, looks painful :( I wish you luck. – Matthieu M. Mar 13 '17 at 15:57
  • *I believe that the problem could be easily solvable with a macro* — macros don't add any real magic to Rust's capabilities, they simply remove the boilerplate. However, you haven't *shown us the boilerplate*. Instead, you've elided the meat of the problem with `...`. I'm pretty sure that you don't want us to create a macro that expands to literal `...`, but we don't know what it **should** be. Please review how to create a [MCVE]. I'd start by providing the complete definition of 2 sets of 2 types and the complete desired code of the `match`. You are likely to get something useful from that. – Shepmaster Mar 14 '17 at 01:52
  • @Shepmaster by `... A ... X ...` I meant `f(A, X)` which I thought was a minimal, complete and verifiable example. In my specific case I need to create an channel to transmit a type associated to a trait implemented by `$first`, then start a thread where I instantiate `$second` and pass it to a functions (`f1`), and call a function (`f2`) on `$first` on the main thread. `f2` requires that `$first` implement a trait whose generic parameter must be an associated type to a trait implement for `$second` and `f1` requires `$second`. – peoro Mar 14 '17 at 02:44
  • 3
    I'd suppose we disagree on the meaning on **verifiable** then, as I can't verify *anything* with the code you've presented and based on my current understanding, it is *impossible* to write the code that you are asking, regardless of using a macro or not. If you [edit] your question to show the complete definition of `A`, `B`, and `Z`, the `match` for the pairs `a, z`, `b, z` and a function signature that you call from the macro, then perhaps we can see the boilerplate and then construct the macro. – Shepmaster Mar 14 '17 at 02:53

2 Answers2

4

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"]);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
apt1002
  • 969
  • 6
  • 15
0

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 of matches and converts them into a single tt. Essentially, it transforms match_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 nested match 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,) to v1, v2, v3 and passes it into the specified callback.
phimuemue
  • 34,669
  • 9
  • 84
  • 115