RFC 2722 by @Nokel81 proposed the following design for overriding the short circuiting operators:
enum ShortCircuit<S, L> {
Short(S),
Long(L),
}
trait LogicalOr<Rhs = Self>: Sized {
type Output;
type Intermediate;
fn short_circuit_or(self) -> ShortCircuit<Self::Output, Intermediate>;
fn logical_or(lhs: Intermediate, rhs: Rhs) -> Self::Output;
}
trait LogicalAnd<Rhs = Self>: Sized {
type Output;
type Intermediate;
fn short_circuit_and(self) -> ShortCircuit<Self::Output, Intermediate>;
fn logical_and(lhs: Intermediate, rhs: Rhs) -> Self::Output;
}
I believe this design can be made simpler like this:
trait LogicalOr<Rhs = Self>: Sized {
type Output;
fn logical_or<F>(self, rhs: F) -> Self::Output
where
F: FnOnce() -> Rhs;
}
trait LogicalAnd<Rhs = Self>: Sized {
type Output;
fn logical_and<F>(self, rhs: F) -> Self::Output
where
F: FnOnce() -> Rhs;
}
These traits take in two arguments, self
and an FnOnce
closure that calculates the right-hand side. The trait implementation decides if it needs to call the closure or not.
For example, this is the implementation for bool:
impl LogicalOr for bool {
type Output = bool;
fn logical_or<F>(self, rhs: F) -> bool
where
F: FnOnce() -> bool
{
if self {
true
}else{
rhs()
}
}
}
impl LogicalAnd for bool {
type Output = bool;
fn logical_and<F>(self, rhs: F) -> bool
where
F: FnOnce() -> bool
{
if self {
rhs()
}else{
false
}
}
}
These traits desugar like this:
<expr a> || <expr b>
=>
(<expr a>).logical_or(||<expr b>)
<expr a> && <expr b>
=>
(<expr a>).logical_and(||<expr b>)