Idiomatic way to express my strange and_then

I have some simple code that is working... but I feel it could be expressed better. Its probably a silly riddle and I appreciate the help.

The code is attempting to take in a path, check that the path is an absolute path, and create said path. I'm realizing now that an if statement would suffice very well in this scenario and now it's a matter wanting to better my understanding of the standard library. I have grown very (too?) fond of monadic flows it seems. TIA

pub fn create(dir: &Path) -> Result<PathBuf, Error> {
    let dir = Ok(dir).and_then(|d| match d.is_absolute() {
        true => Ok(d),
        false => Err(Error::NotAbsolute(d.to_path_buf())),
    })?;
    fs::create_dir_all(dir)?;

    Ok(dir.to_path_buf())
}

That just looks needlessly complicated. What's wrong with:

pub fn create(dir: &Path) -> Result<PathBuf, Error> {
    if !d.is_absolute() {
        return Err(Error::NotAbsolute(d.to_path_buf()));
    }
    fs::create_dir_all(dir)?;
    Ok(dir.to_path_buf())
}
4 Likes

An if as in @jwodder's reply is more idiomatic.

Not necessarily idiomatic or recommended, but...

pub fn create(path: &Path) -> Result<PathBuf, Error> {
    path.is_absolute()
        .then_some(())
        .ok_or_else(|| Error::NotAbsolute(path.to_path_buf()))
        .and_then(|_| Ok(fs::create_dir_all(path)?))
        .map(|_| path.to_path_buf())
}

Side comment: I note that you return a PathBuf in some form for all cases except an io::Error from create_dir_all. If that call fails, you won't have the path information. Also, apparently, you have a From<io::Error> for Error implementation.

The resulting error is going to lack context (the path) and probably be pretty vague.[1] Here's a longer article about making great Rust errors. But the simpler, better-though-not-great option is to have a specific variant for directory creation and some constructor that keeps the path name.


  1. Could be permission denied, but could also be something like file not found (because an intermediate directory does not exist) ↩ī¸Ž

2 Likes

@quinedot beat me to it, but from your original example you might find this variation also interesting

pub fn create(dir: &Path) -> Result<PathBuf, Error> {
    dir.is_absolute()
        .then(|| fs::create_dir_all(dir))
        .map_or_else(
            || Err(Error::NotAbsolute(dir.to_path_buf())),
            |res| res.map(|_| dir.to_path_buf()),
        )
}
1 Like

For sure an if statement is the way to go, I have taken that approach instead :sweat_smile:. Regardless, thanks for playing along and showing me a better alternative to what I had. I have a lot to studying left before I am fluent with all of the available result and option methods. Oh and thanks for the error link, I'll give it a read.