A different way to override || and &&

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>)

Desugaring to a closure doesn't work when the right-hand side contains flow control expressions (like return or break or continue). For example:

'outer loop {
    let x = foo || break 'outer 5;
}
9 Likes

damn, so Nokel's solution is better here

Wouldn't the ControlFlow enum avoid this? It could be desugared to the following unless I'm missing something.

'outer loop {
    let x = foo.logical_or(|| ControlFlow::Break(5));
}

Admittedly this could get somewhat complicated if there's yet another level of nesting, but I still think it's possible.

Even if logical_or known there's some kind of control flow involved it has no way to actually implement it since it has no way to know about the outer loop. Also, ControlFlow doesn't cover the Return case.

Another problem with closures is that they can't temporarily move out of local fields. For example:

let mut a = String::new();
let x = foo || {
    let b = a;
    a = b;
    0
};
drop(a); // use a

If we used a closure there then a would be permanently moved inside it, making the drop(a) invalid.

2 Likes

Pre-RFC thread where the topic was thoroughly discussed.

2 Likes

Why not just use Try (V2)?

{ x && y }

try { x?; y? }

{ x || y }

try { x!?; y!? }

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.