Today I published
if_chain, a macro for writing nested
if let expressions. The documentation does a good job of showing how to use the crate, and I recommend taking a look through it. This article will instead go more into the background behind this macro and how it helped with my own projects.
As you can see, a common issue was rightward drift. Every
if statement would indent the code by one more step, such that the actual message ended up off the page!
Now, Rust does provide tools for tackling this issue; and in most cases it would be enough to use them. But for my use case—writing lints—they are not enough:
We can rewrite each check to yield an
Option, and use
.and_then() or the
? operator to chain them. But when writing a lint, the interfaces involved are so broad and irregular that wrapping everything is not practical.
We can try to merge all these checks into a single pattern. But in this case, the intermediate nodes are wrapped in smart pointers (the
P type), and current Rust doesn’t have a way to dereference a smart pointer from within a pattern.
I wasn’t the first to run into this problem. rust-clippy, a collection of general-purpose lints, has a utility macro called
if_let_chain! for this purpose. Using this macro, the example above would be written like this instead:
This solved the rightward drift problem at hand. But as I used the macro, I found a few flaws in the implementation:
if_let_chain! is a part of Clippy, I would have to either copy-and-paste the macro, or depend on the whole of Clippy. It would be better if the macro was in its own crate.
When inspecting the type of an expression, for example, the code involved can be quite long. One would use intermediate variables (
let statements) to keep the code easy to read. But since
if_let_chain! expects every line to be an
if let, there’s no good way of doing this refactoring.
Some of the syntax choices, like omitting the
if from each check and the use of square brackets, seem arbitrary to me. I’d prefer it if the macro looks more like the generated code.
if_chain comes in. It addresses the points raised above, and adds some features of its own:
if_chain! lets you give an
else clause, which is evaluated when any of the checks fail to match.
Multiple patterns. Rust allows for matching multiple patterns at once in a
match expression. For example, this code:
prints “one or two.”
if_chain! supports this syntax in
if let as well.
Our example now looks like this: