Making if let notation usable in more kinds of conditionals


#1

^ 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


#2

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


#3

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

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.


#5

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


#6

Maybe you could use || analogously to | patterns?


#7

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


#8

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.


#9

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/