Or operator on options

I want to be able to say:

optionastatement || optionbstatement

is there anyway to achieve this? or_else won't cut it for obvious reasons.

There's no way to do that yet. There have been past suggestions of a trait like LogicalOr, but those have had drawbacks that (I'm guessing) are similar to what you don't like about or_else. Still, please do specify what you think those "obvious" reasons are.

1 Like
  1. Variable scope
  2. I can’t have ? Operator on a result in optionbstatement if it is a closure even my enclosing function returns a result

Besides, or_else looks really ugly:-) a || b is really elegant Maybe come up an official macro would do the job too.

I just realize that there are no logical operator traits in Rust. Maybe it is on purpose? Logical operators are hard to mimic?

It's not clear how does it interreact with short circuiting.

What's the desired semantic? Can you provide a desugar-ed code?

2 Likes

We could attach this to Try now, where $a || $b becomes

match Try::branch($a) {
    ControlFlow::Continue(a) => Try::from_output(a),
    ControlFlow::Break(_) => $b,
}

(likely with the same "same residual" inference rules as try blocks are likely getting).

Given an invert_try<T: Try>(_: T) -> impl Try<Output=T::Residual, Residual=T::Output>, then $a || $b can be written as uninvert_try(try { invert_try($a)?; invert_try($b)? }) (though ofc this introduces a new ? boundary that cklein would like to avoid).

If you also allow $a && $b as a shorthand for try { $a?; $b? } and $a ?: $b for ($a || try{$b}).unwrap() (both in match form), then &&/||/?: become very powerful generic monadic combinators if you're discarding the residual.

However, these are (like monads) so general to the point of having unclear benefit. Even when you're fully monad wise, nested monads (i.e. using ? to break from the fn from expressions being monad applied with ||) are hard to wrap your head around.

I personally think an easy method for Option<T> -> ControlFlow<T> (essentially invert_try for Option) makes sense, so that the OP's $a || $b can be written as e.g. try { $a.then_break()?; $b.then_break()? }.break_value() makes sense, but going further to &&/||/?: goes a bit too far beyond what Rust wants to encourage. (Not the least because these encourage ignoring the residual, whereas the purpose of ? is in major part to encourage not discarding the residual.)

Aside: this makes me wonder whether the macro hygiene of break/continue/return/?/etc. sh/could've included the break label/target, such that macros can't change their target. I've defensively written unsafe-using macros in awkward ways[1] to avoid the user code to interfere, before.


  1. The simplest and completest is just to put the user code in an immediately-invoked closure. The fancier way is to write something along the lines of

    // label for macro code to target
    // (do macro stuff additionally wherever)
    'macro: loop {
        // require user code to exit straightline or diverge
        let false = true else {
            // capture unlabeled break/continue
            for () in Some(()) {
                $user_code
                // actually exit
                break 'macro;
            };
        };
    }
    

    if you want to preserve return and .await permission. (Presented without warranty of any kind, including but not limited to that of fitness for any purpose.) ↩︎

2 Likes

A?.B | c?.d Assume my enclosing function return an anyhow result

Now if I use or_else, I would have to put c?.d in closure, it won’t work. Or else can do short circuiting, but closure does not have the same scope as statement

I finally created a fallback! macro myself to do the job. hope it gets into std. :slightly_smiling_face:

macro_rules! fallback {
    ($a:expr) => { $a };
    ($a:expr, $($b:expr),*) => {if let Some(x) = $a { x } else { fallback!($($b),*) } };
}

I generally use a.ok().and_then(|a| b)? or a.ok().or_else(|| b)? if I want to do some fallible stuff in b, but it's still clumsy.

You might also be interested in this (very old) thread: Something for coalescing; aka generalized/improved `or_else`

std::ops::Try is not stable after so many years, I don't think it will ever make into stable release. There must be something seriously wrong.

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