Composition in Rust
I’ve been playing around with Rust a lot lately. I was really inspired by a guy named Tom Leys, who used the Godot game engine’s NativeScript feature combined with the Godot C bindings for Rust to make a crazy space factory game. Seems to be in development still but looks pretty amazing. You can read about his adventure here. But Rust is gaining ground everywhere lately, it would seem. Rust is a very capable language and you have a lot of options (every option?) when it comes to build targets.
Anyway, I love the luxury of having access to functions like compose
in JavaScript. I finally am feeling like I’m not fighting Rust’s compiler to write code, so I decided to dive into the Rust Macro system. And I must say, once you understand the rules it’s really nice to work with.
What new rustaceans need to know right away about writing macros in Rust is that you’re just writing match arms, with some really different syntax.
Basically a macro is made of the pattern part in parenthesis and the expansion in brackets. Truth be told you can use whatever delimiter ( [],(),{}
) for these sections, but it looks like most people go with the () => {}
pattern.

You can also capture parts of the token stream. The Little Book of Rust Macros explains all of them and their purposes in detail, plus some really nifty patterns to exploit within the rust macro system. For my next example you’ll only need to know that an ident
token is any identifier (something you’ve declared), and tt
is a raw token stream and can basically refer to anything you’ve written.

So the pipe
macro I’ve defined above is intended to be invoked like a function. It has only one match arm, which expects an initial value followed by |>
and one or more identifiers, which I expect to refer to functions or closures. You’ll see the variables in the match arm are marked with $
. The $
before an identifier in a macro means, ‘capture this part of the token stream as a variable I can refer to’. There are two captures in my pipe
— $init
and $f
. I’ve marked $init
as a tt
(raw token stream) mainly because I can now follow that token with the|
(OR) token. If that was marked $init:expr
(as an expression), the compiler would be mad about following it with |
. The second capture is a repeating capture, as noted by its $(),+
wrapper. Woah, what’s that now? Let’s break that down:
$()
declares that the contents of the parenthesis is a repeating capture or expansion, depending on the side of the match arm.,
denotes the delimiter that will be expected between each capture or expansion. No delimiter may also be used, such as in the case where you want to simply repeat a few expressions or a code block.+
says, ‘this pattern expects to capture/expand to 1 or more of itself’. (*
is for 0 or more,?
is 0 or 1).
Hopefully that makes more sense now. So onto the expansion side, the first thing an astute reader will notice is the doubled curly brackets. That just means that inside the first set of curly brackets (which is the expansion) we want our macro to expand into a block (the second set of curly brackets). Then I declare the result variable r
as equal to the $init
capture. Now we can thread that value through each of our functions, reassigning r
to the result of the function called each time. That’s what our second repeating capture, $( r = $f(r); )*
, does. Finally, we return r
.
There’s one more thing we can do to make this macro more correct, and that is convert our $init
capture from a tt
to an expr
using an AST Coercion macro (as defined in The Little Book of Rust Macros).

So let’s put that into our pipe
.

That will convert our raw tt
capture to an expr
. That’s good because it will now tell you that $init
was expected to be an expression even though it’s labeled as tt
.
Now that we’ve gotten a forward pipe, which threads it’s $init
through the listed functions from left to right, let’s define a compose that threads its $init
from right to left. Turns out, it’s way harder to write a proper compose than my hacked-together pipe
macro. I spent more time than I would like to admit failing at writing a good definition. Credit to this guy for figuring out the most elegant solution!

It’s so very beautiful because it spells out exactly the category theory that goes into the definition of compose, you have a function F(A) -> B
and a function F(B) -> C
and you combine the two to get the result function F(A) -> C
. It’s also incredibly simple. Just two match arms can describe any number of consecutive compositions by taking advantage of repeating captures. That makes it a variadic compose. It can take any number of functions as arguments and connect them together just by nesting 2-function composition.
Hope you enjoyed another functional journey. Until next time, FP on!