More extensive use of Result could yield safer, clearer code

I'm curious about this situation:

fn main() {
    foo()
}

fn foo() -> () {
    let hello_world = "Hello, world!";
    let res = println!("{}", hello_world); 
    res
}

While this isn't likely to fail, I imagine there are cases where it could. Arguably, anything involving I/O could fail. It seems like requiring the signature to be fn foo() -> Result<(), Error> would be appropriate here. I'm not aware of any major usability issues with this, as you could always explicitly panic if needed. This also makes it more clear that yes, foo is doing something that might fail. If we wanted to be clear it was I/O related we could introduce a type alias to Result<(), Error> for clarity, but I already think making the more fundamental change would be enough for most.

I'm sure there are other instances like this, not just println!, but it is the one I've seen the most.

You can opt-in to fallible stdout writing already

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    foo()?;
    Ok(())
}

fn foo() -> io::Result<()> {
    let hello_world = "Hello, world!";
    writeln!(io::stdout(), "{}", hello_world)?;
    Ok(())
}

println! is a convenience macro for use in cases where you always expect stdout will be writeable, more serious applications should be using logging libraries and writeln!(stdout to detect error conditions (and also Stdout::lock for better performance).

14 Likes

And if you're logging things, consider printing to stderr instead with eprintln!(); if you don't want to pull a dependency. Stdout should only be for actual output that another program might want to consume, stderr is for all logging things related (error reporting, progress, etc.).

2 Likes

Doccy side note: Some variations on println! are mentioned in the docs, but no mention is made of writeln! or Results. Which is all to say, some of this discussion could be added to the docs.

You can fix it!

1 Like