Avoid multiple calls to `map_err` in a chain of Results

Viewed 61

I am working with some code that uses the libgit2 bindings for Rust to perform operations on a Git repository.

I have a section of code that transforms a "committish" reference (something that either is a commit or ultimately references a commit, like a tag) that currently looks like this:

        let mut target_commit = target_object
            .peel(git2::ObjectType::Commit)
            .map_err(|_| anyhow!("Target `{commitish}` cannot be evaluated as a commit"))?
            .into_commit()
            .map_err(|_| anyhow!("Target `{commitish}` cannot be evaluated as a commit"))?;

I'd like to avoid having two identical calls to map_err in that chain. Based on the description of the and_then combinator...

Calls op if the result is Ok, otherwise returns the Err value of self.

...I thought maybe this would work:

        let mut target_commit = target_object
            .peel(git2::ObjectType::Commit)
            .and_then(|c| c.into_commit())
            .map_err(|_| anyhow!("Target `{commitish}` cannot be evaluated as a commit"))?;

But that fails with:

106 |             .and_then(|c| c.into_commit())
    |                           ^^^^^^^^^^^^^^^ expected struct `git2::Error`, found struct `git2::Object`

What's the correct way to simplify this expression?

1 Answers

Simplification could mean multiple things. One way to simplify is to remove the duplication of the anyhow!() invocation.

Since you don't actually use the error information, you can use Result::ok to discard the error information by converting the Result<T, E> to Option<T>, then use Option::ok_or_else at the end of a chain to turn the final Option back into a Result.

let mut target_commit = target_object
    .peel(git2::ObjectType::Commit).ok()
    .and_then(|c| c.into_commit().ok())
    .ok_or_else(|| anyhow!("Target `{commitish}` cannot be evaluated as a commit"))?;
Related