Composition in Rust

Ross
Ross
Oct 24, 2020 · 5 min read
Image for post
Image for post
Photo by Weston MacKinnon on Unsplash

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.

Image for post
Image for post

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.

Image for post
Image for post

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).

Image for post
Image for post

So let’s put that into our pipe.

Image for post
Image for post

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!

Image for post
Image for post

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!

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Ross

Written by

Ross

Programming maniac, #JavaScript zealot. I'm crazy about #FunctionalProgramming and I love Rust.

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Ross

Written by

Ross

Programming maniac, #JavaScript zealot. I'm crazy about #FunctionalProgramming and I love Rust.

The Startup

Medium's largest active publication, followed by +771K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store