Making if let notation usable in more kinds of conditionals

^ I thought of this idea a couple of times too.

As for if true && let (1,) = (2,) { }, it seems like we should expect boolean value at right hand side of &&. So I guess we need a new binding syntax here.

what we can do now is to if let (true, (1,)) = (true, (2,)) { }, which is verbose.

so maybe if let (1,) = (2,) where true { } is good enough

and we might also introduce while let Some(x) = iter.next() where x > 42 { }

In case we need to match 2 patterns at the same time and deal with else-case in one place, maybe if let Some(x) = foo, Ok(y) = bar { ... } else { ... }, that is, use comma to separate multiple pattern-matching, but this syntax won't fit for boolean conditions.

so maybe combine these two if needed: if let Some(x) = foo, Ok(y) = bar where cond { ... } else { ... }

Instead of where, maybe when as a contextual keyword will look better

Why not match on pairs? This should work today if let ((Some(x), Ok(y)) = (foo, var) { ... } else { ... }

The usefulness of this pattern is most apparent in situations where:

  1. You want the conditional to test a value that gets matched and bound in the same expression so you can’t use it yet.

  2. Matching and binding it in an outer if let and then having a nested conditional creates a mess if you want to have else blocks that correspond to the else case of the “whole” operation.

An example:

// Possible to have conditionals ATM:
if let (Some(inner), true) = (wrapped, local_var > 10) { ... }

// Not possible to have conditionals that depend on a value bound here:
// if let Some(inner) && inner > 10 { ... }

// So you nest the conditional:
if let Some(inner) = wrapped { if inner > 10 { ... } }

// But you lose the ability to do an else on the joint conditional and must duplicate:
if let Some(inner) = wrapped {
    if inner > 10 { ... }
    else { println!("Nope"); }
} else { println!("Nope"); }

// ...or use an auxillary variable
let mut nope = false;
if let Some(inner) = wrapped {
    if inner > 10 { ... } else { nope = true; }
} else { nope = true; }
if nope { println!("Nope"); }

// Which both feel ugly and unergonomic :(
4 Likes

Yeah, I’ve been wanting to use all kinds of things:

  1. && or || initial match with further testing on the bound value
  2. && or || initial match with another match
  3. else or else if with further matching

In general, it would be nice if the let match pattern behaved more like a common bool-yielding expression with the additional side effect of binding the matched object – I guess this would depend on non-lexical lifetimes though.

From the ergonomics tracker (https://github.com/rust-lang/rust-roadmap/issues/17), looks like else match (as in https://github.com/rust-lang/rfcs/pull/1712) was scoped out, so I’m not sure how that would affect the stuff discussed here.

1 Like

|| doesn't make any sense here, does it? If the pattern did not match, surely you cannot use the bound variable.

Maybe you could use || analogously to | patterns?

|| doesn't make any sense here, does it? If the pattern did not match, surely you cannot use the bound variable.

Fair point, that part might not make sense.

It could make sense, in theory:

if let Some(inner) = wrapperA || let Some(inner) = wrapperB {
    println!("Got a non-None value: {:?}", inner);
}

But the “&& in if let” use cases are probably more compelling.

Btw. there is some prior art not only in Swift, but also in C# 7.0: (Check the subchapter “Pattern matching”)

https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/

1 Like

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