How do you handle only the Err part of a Result?

Sometimes I have function calls that return a Result<T, E> where I would like to disregard the success and handle the error case. e.g.

I could write this, but I don't like that Ok that does not do anything. I guess I could wrap this in a macro, but I am quite sure this is a common use-case that already has a better solution.

let text = "Hello World!";
match std::fs::write("x/out.md", text) {
    Ok(_) => (),
    Err(err) => {
        eprintln!("Could not write the file {err}");
        call_another_function();
    }
}
if let Err(err) = std::fs::write("x/out.md", text) {
    eprintln!("Could not write the file {err}");
    call_another_function();
}
4 Likes

That's pretty much exactly what I would do. You can shorten it like quinedot indicated, but I generally don't because only handling the Err case is kinda weird.

One important question I keep in mind is: "does this code clearly distinguish between 'valid but unintended' and 'valid and intentional'?". Having an explicit Ok(_) => { /* ignore */ } arm makes it clear that I meant to do this.

1 Like

Thanks. That's nice, however I really don't understand this "assigning to Err(err)" thing. That's not a variable. Could you give me a hint or link?

good point and do you really add that comment?

Yes. Just writing Ok(_) => () doesn't convey my intent: did I mean to map to a () value specifically, or was it auto-generated by the IDE, or was it simply a valid (but inert) placeholder?

The comment is completely unambiguous.

I'm constantly reading my own code back, and trying to imagine myself reading it in 6 months when I've completely forgotten what I was thinking. If I'm not completely certain I'll be able to determine the code's intent from scratch, I clarify.

Heck, I'll even sometimes use match some_bool { true => .., false => .. } to make absolutely clear that I am handling both branches and didn't forget one by accident.

1 Like

The Book has a section on it: Concise Control Flow With if let.

3 Likes

It's a fallible pattern, and there's more documentation here. Regular let [pattern here] = ... statements also use (infallible) patterns, not only variable names.

I also feel motivated to elaborate that my first reply was just a mechanical solution to the OP.[1] What I almost always really do for IO errors is more like

fn my_method(&self, ...) -> Result<Ty, SomeCustomError> {
    // ...
    let _ = io::write(path, data).map_err(|e| {
        // Add some context like the path name to create
        // the SomeCustomError
    })?;
}

But given your OP this likely means changing your larger API. Propogating errors isn't always the solution, but is quite common.

Incidentally, you could avoid the map_err if you just returned an io::Result or if SomeCustomError: From<io::Error>, but then you lose the context of what filename you were trying to write. (Making really good errors is a big pain, but when it involves critical information like filenames, I find it invaluable.)


  1. I don't find it lacking in information in this case, because it's clear enough to me that the Ok is being dropped, if you needed the Ok value later on you'd have to store it, and in this case I know offhand that Ok variant is a () to boot. ↩ī¸Ž

2 Likes

Since fs::write returns Result<(), E>, another way to do this is something like

match std::fs::write("x/out.md", text) {
    Ok(()) => {},
    Err(err) => {
        eprintln!("Could not write the file {err}");
        call_another_function();
    }
}

Because not doing something with () is normal.

3 Likes