Clarification regarding idiomatic input

Hello everyone!

Is more idiomatically to use

io::stdin().read_line(&mut s).unwrap_or_else(|err| {
        eprintln!("Critical error: failed to read from stdin. Reason: {err}");
        process::exit(1);
    });

than

io::stdin().read_line(&mut s).expect("Critical error: failed to read from stdin");

?

Each has it's own disadvantages:

unwrap_or_else includes a side-effect in a closure. This is a rare case where I'd feel OK with that. But ... the call to process::exit() terminates abruptly with no unwinding and doesn't call drop on anything. That can be problematic depending on what you've already done earlier in your binary. (see my "rant" here)

expect doesn't include the error message in the output but at least it panic!s so drop gets called.

most idiomatic, but not most common ;), would be playground

use std::io::{self, ErrorKind};

fn main() -> io::Result<()> {
    let mut s = String::new();

    io::stdin()
        .read_line(&mut s)
        .map_err(|err| std::io::Error::new(ErrorKind::InvalidInput, err))?;

    Ok(())
}

(or use exit_safely for more control over the exit code[1])


  1. blatant plug & disclosure - I'm the author & maintainer ↩︎

But so what about simple '?' after read_line:

io::stdin().read_line(&mut s)?;

instead of mapping error?

Well, ideally you'd go a step further and define your own error type.

The goal is to have a control over what might go wrong in the application and avoid that hell where anything can come from anywhere.

You could and it would still be a decent option. That means you'd get a potentially less meaningful error message, as you'd not have the context that it was from trying to get input...

It's probably worth building the binary and briefly giving it something invalid to see whether you're happy with the resulting error message ...

Definitely

let s = std::io::stdin().lines().next().unwrap().unwrap();

Alternatively you could start with figuring out who the user is, then decide how you want the code to respond.