Map_err usage in 'the book' example

Trying to understand how the following example from the Rust book is supposed to work:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt").map_err(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Tried to create file but there was a problem: {:?}", error);
            })
        } else {
            panic!("There was a problem opening the file: {:?}", error);
        }
    });
}

When a file doesn't exist it creates a new one but returns Err(File) which doesn't look right.
Am I missing something?

It returns a new file, if the new file can't be made, it panics and print the error.

No it does not return just File.
The type of f is std::result::Result<std::fs::File, std::fs::File> which, while valid, is rather strange.
When the file exists the value of f is Ok(File), and when it does not, it is Err(File).

Yes, sorry, the first exemple with match should result in two unwrap_or_else(). Even the errors are not the same, in one it's the ErrorKind variant and in the other it's the complete file open io::Error.
Maybe @carols10cents will be able to tell us more about it.

The book example is definitely erroneous. The following is a minimal fix. Its interesting to revisit this chapter though, because I don't think I would currently approach the problem this way, "seasoned Rustacean" or otherwise.

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Tried to create file but there was a problem: {:?}", error);
            })   
        } else {
            panic!("There was a problem opening the file: {:?}", error);
        }
    });
    
    dbg!(f);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.53s
     Running `target/debug/playground`
[src/main.rs:15] f = File {
    fd: 3,
    path: "/playground/hello.txt",
    read: false,
    write: true
}

1 Like

Created a bug with the book for this, with some more details:

https://github.com/rust-lang/book/issues/1872

Yep, this is indeed a bug and it was fixed at the end of December. The book rides the release trains; the fix is on beta so it'll be fixed at The Rust Programming Language - The Rust Programming Language with the next release. Thanks!

1 Like

Thanks everyone who answered.
Another question, a theoretical one. It seems that having a reasonable trait bound on the E type (of Result<T,E>) would help compiler to catch this bug. Any particular reason it is not there?

I'm sure there might be some good reason for it, but it seems unfortunate that simple errata like this would be withheld from the "stable" book for many months. I'll just have to remind myself to always look at The Rust Programming Language - The Rust Programming Language , now that I know this. Thanks!

There were some other odd things about this particular example, but my above referenced issue was closed without further comment. Lost in the volume of issues I guess.

@gera-k: It was a design choice that now can't be changed. There are some potentially legitimate cases for returning non-std::error::Error types as the E in Result<T, E>, for example:

Here, one could argue that some other enum besides std::result::Result could have been used. I know I've seen some other cases of functions where something is passed by value (moved), and in the event of a failure, passed back as the Err(E) but I couldn't find one immediately in libstd. Anyone?

In the ecosystem there have been other cases where even custom error types aren't std::error::Error, usually just an unintentional omission.

1 Like

OsString in std::ffi - Rust is an example

1 Like

Coordinating the infrastructure between the rust-lang/book repo that has a separate development process from rust-lang/rust, the book being included with Rust installations, and the website that otherwise has documentation that coordinates with the Rust release cycle, and everyone who works on Rust and uses all of these resources being able to keep track of what is in what state where, is a complex and not easily solvable situation.

Ah, I didn't even keep reading after I saw master already had the code in the example set to map_err. I only expected there to be one problem per issue! Now that I've read the rest, I agree it's not production-quality code, but the example is illustrating how to take different actions based on some aspect of the error value. It's hard to create examples that are small but "realistic" and illustrate the point we're trying to get at. In the alternative you provided in the issue, we'd have to explain OpenOptions first.

1 Like

Fair enough. I only really noticed it because of the OP's bug. Thanks for taking the time to read it there and reply here, @carols10cents.

1 Like