Multiple pattern rules
Let's write a macro that will simplify the implementation of the traits to overload operators. This macro will have two rules: one for the + and one for the - operators. Here's the first rule of the macro:
macro_rules! op { (+ $_self:ident : $self_type:ty, $other:ident $expr:expr) => { impl ::std::ops::Add for $self_type { type Output = $self_type; fn add($_self, $other: $self_type) -> $self_type { $expr } } }; // …
In this pattern, we use other types of syntactic elements: ident, which is an identifier, and <span>expr, which is an expression. The trait (::std::ops::Add) is fully qualified so that the code using this macro won't need to import the Add trait.
And here's the rest of the macro:
(- $_self:ident : $self_type:ty, $other:ident $expr:expr) => { impl ::std::ops::Sub for $self_type { type Output = $self_type; fn sub($_self, $other: $self_type) -> $self_type { $expr } } }; }
We can then use this macro with our Point type, like this:
op!(+ self:Point, other { Point { x: self.x + other.x, y: self.y + other.y, } }); op!(- self:Point, other { Point { x: self.x - other.x, y: self.y - other.y, } });
Let's see how the matching works:
For the first macro call, we start with +, so the first branch is taken because it matches +, which is the start of this branch. Next we have self, which is an identifier, so it matches the ident pattern and this is assigned to the $_self meta-variable. Then, we have : which matches the colon in the pattern. After that, we have Point, which matches the $self_type meta-variable of the ty type (for matching on a type). Then we have , which matches the comma in the pattern. Next, we have other, which matches the next item in the pattern, which is the $other meta-variable of the ident type. Finally, we have { Point { … } }, which matches the expression required at the end of the pattern. This is why these macros are called macros by example, we write what the call should look like and the user must match the example (or pattern).
As an exercise to the reader, try the following:
- Add the missing operators: * and /
- Add the ability to specify the types of $other and the return type in the pattern
- If you haven't already done this in the previous point, add more tokens so that it looks more like a function declaration: +(self: Point, other: Point) -> Point { … }
- Try moving the operator in the pattern after the $self_type meta-variable to see the limitations of macro_rules