Description
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.