Chaining matches

Is this hard to understand?
Is using consecutive matches and blocks confusing?

use std::io;
use std::io::BufRead;

fn main() {
    let stdin = io::stdin();
    let lines_iter = stdin.lock().lines();

    for line in lines_iter {
        // is this confusing?
        match match line {
            Ok(line) => Command::parse(&line),
            Err(e) => {
                println!("Error, unable to read line: {}", e);
                Command::Quit
            }
        } {
            Command::Quit => break,
            command => call(command),
        };
    }
}

I can't say I've seen a match match in the wild before, and if this were a code review I'd probably ask the author to split it into two statements.

I can't put the reasoning into words, but I'd say it's kinda like how you might split a long and complicated boolean expression full of && and ||'s into smaller pieces, putting a name to the idea each sub-condition is coding for.

3 Likes

I find it extremely confusing!
I would recommend to use idiomatic error handling, something like

fn main() -> Result<(), Error> {
    // ...
    for line in lines_iter {
        let line = line?;
        let command = Command::parse(&line)?;
        call(command);
    }
}

As a good rule of thumb, the more linear the code looks like (the less nesting), the more it will be understandable.
And by chaining matches, you not only added two levels of nesting, you also "obfuscated it" which add cognitive load itself.

4 Likes

That's a good way of putting it... the more indentation/nesting you have, the more you need to keep in your head when reasoning about the code. Doubly-so for constructs you don't see too often.

1 Like

A couple of days ago, we had return return return return return, now we have match match too :sweat_smile:

3 Likes

I missed the chained returns post. Do you have a link

Sure! Here it is.