3
votes

I wrote a macro for sym-linking files. At first I had just the first pattern, but then thought that it would be nice not to have to write "&format!" all the time.

So with these patterns:

macro_rules! syml {
    ($a:expr, $b:expr) => {
        Command::new("ln").args(&["-s", $a, $b])
    };
    ( ($a:expr, $($x:expr),+), ($b:expr, $($y:expr),+) ) => {
        Command::new("ln").args(&["-s", &format!($a, $($x),+), &format!($b, $($y),+)])
    }
}

I want to match these cases:

syml!("from", "to");
syml!(("{}", from), "to");
syml!("from", ("{}", to));
syml!(("{}", from), ("{}", to));
syml!(("{}{}", from, here), ("{}{}", to, there));

But so far every time only the first pattern is matched, so I'm getting mismatched types errors like expected reference, found tuple.

I don't understand why, even for the last two example cases, it tries to match the first pattern and not the second.

1
Not a macro specialist, so I'll just make 2 quick points: (1) a tuple is an expression, so "(x, y)" can match $a:expr and (2) I believe the patterns are tried in order... have you tried putting the more specific pattern first? - Matthieu M.
Not related to the problem, but the standard library has a symlink function. - Francis Gagné

1 Answers

4
votes

As @MatthieuM points out in a comment, a tuple is an expression and macro rules are tried in order.

So in your case:

macro_rules! syml {
    ($a:expr, $b:expr) => {
        Command::new("ln").args(&["-s", $a, $b])
    };
    ( ($a:expr, $($x:expr),+), ($b:expr, $($y:expr),+) ) => {
        Command::new("ln").args(&["-s", &format!($a, $($x),+), &format!($b, $($y),+)])
    }
}

The first rule will always match any time the second would. The solution is to swap them around:

macro_rules! syml {
    ( ($a:expr, $($x:expr),+), ($b:expr, $($y:expr),+) ) => {
        Command::new("ln").args(&["-s", &format!($a, $($x),+), &format!($b, $($y),+)])
    };
    ($a:expr, $b:expr) => {
        Command::new("ln").args(&["-s", $a, $b])
    }
}

(Playground)

The above doesn't cover two of your test cases where you mix tuples and strings:

syml!(("{}", from), "to");
syml!("from", ("{}", to));

This can be simply fixed by adding new cases (in order). (I don't know if it's possible to factor out the tuple/string matching, but would be interested in seeing any solutions.)

macro_rules! syml {
    ( ($a:expr, $($x:expr),+), ($b:expr, $($y:expr),+) ) => {
        Command::new("ln").args(&["-s", &format!($a, $($x),+), &format!($b, $($y),+)])
    };
    ( $a:expr, ($b:expr, $($y:expr),+) ) => {
        Command::new("ln").args(&["-s", $a, &format!($b, $($y),+)])
    };
    ( ($a:expr, $($x:expr),+), $b:expr ) => {
        Command::new("ln").args(&["-s", &format!($a, $($x),+), $b])
    };
    ($a:expr, $b:expr) => {
        Command::new("ln").args(&["-s", $a, $b])
    }
}

(Playground)