Skip to content

ACP: Add methods for converting bool to Result<(), E> #606

Open
@LimpSquid

Description

@LimpSquid

Proposal

Problem statement

A common pattern found in application code is the conversion of a bool to a Result<(), E> and propogating the error with the ?-operator. Usually this leads to an intermediate conversion with bool::then_some and using ok_or[_else] to convert the boolean to an Err.

Motivating examples or use cases

Examples with the intermediate bool::then_some conversion can be found throughout many Rust codebases:

A simple example is given down below which will be used as base reference for the solution sketch.

#[derive(Debug)]
struct InvalidSyllable;

fn validate_syllables(syllables: &[&str]) -> Result<(), InvalidSyllable> {
    let allowed_syllables = [ "nan", "a", "ba" ];
    
    syllables
        .iter()
        .all(|syllable| allowed_syllables.contains(syllable))
        .then_some(())
        .ok_or(InvalidSyllable)
}

I propose new methods bool::then_err and bool::else_err for converting a boolean directly to a Result<(), E>, simplifying common patterns, aid in readability and reducing repetition.

Solution sketch

The snippet from the previous paragraph is refactored to make use of the proposed methods. For example purposes this is done with the use of a custom trait, but is expected to be implemented as methods on bool.

trait BoolExt: Sized {
    fn then_err<E, F: FnOnce() -> E>(self, f: F) -> Result<(), E>;
    fn else_err<E, F: FnOnce() -> E>(self, f: F) -> Result<(), E>;
}

impl BoolExt for bool {
    fn then_err<E, F: FnOnce() -> E>(self, f: F) -> Result<(), E> {
        match self {
            true => Err(f()),
            false => Ok(()),
        }
    }

    fn else_err<E, F: FnOnce() -> E>(self, f: F) -> Result<(), E> {
        match self {
            true => Ok(()),
            false => Err(f()),
        }
    }
}

#[derive(Debug)]
struct InvalidSyllable;

fn validate_syllables(syllables: &[&str]) -> Result<(), InvalidSyllable> {
    let allowed_syllables = [ "nan", "a", "ba" ];
    
    syllables
        .iter()
        .all(|syllable| allowed_syllables.contains(syllable))
        .else_err(|| InvalidSyllable)
}

fn main() {
    println!("{:?}", validate_syllables(&["ba", "nan", "a"]));
    println!("{:?}", validate_syllables(&["ba", "boon"]));
}

Alternatives

Could this be written using existing APIs?

Yes, using an intermediate then_some(()) is a valid approach used by many existing codebases. Alternatively one may decide to directly use the expression in an if-conditional or use an intermediate let-binding in case the conditional becomes hard to read.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ACP-acceptedAPI Change Proposal is accepted (seconded with no objections)T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions