lambda fairy

Announcing if_chain

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.

The problem

As part of another project, I was working on a lint plugin that catches common mistakes and suggests ways to fix them. Unfortunately, the code I wrote would often look like this:

if let ExprCall(ref path_expr, ref args) = expr.node {
    if let Some(first_arg) = args.first() {
        if let ExprLit(ref lit) = first_arg.node {
            if let LitKind::Str(s, _) = lit.node {
                if s.as_str().eq_ignore_ascii_case("<!doctype html>") {
                    // ...

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:

Existing solutions

I wasn’t the first to run into this problem. 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:

if_let_chain! {[
    let ExprCall(ref path_expr, ref args) = expr.node,
    let Some(first_arg) = args.first(),
    let ExprLit(ref lit) = first_arg.node,
    let LitKind::Str(s, _) = lit.node,
    s.as_str().eq_ignore_ascii_case("<!doctype html>"),
    // ...
], {
    // ...

This solved the rightward drift problem at hand. But as I used the macro, I found a few flaws in the implementation:

Introducing if_chain

Here’s where if_chain comes in. It addresses the points raised above, and adds some features of its own:

Our example now looks like this:

if_chain! {
    if let ExprCall(ref path_expr, ref args) = expr.node;
    if let Some(first_arg) = args.first();
    if let ExprLit(ref lit) = first_arg.node;
    if let LitKind::Str(s, _) = lit.node;
    if s.as_str().eq_ignore_ascii_case("<!doctype html>");
    // ...
    then {
        // ...
Yuuko Aioi raises a pair of chopsticks over her head