If let pattern lazy evaluation

The code below will print a and b, but I'd like it to print only a.

Looking at the line if let (true, None) = (a(), b()) is there any way to make b() be called lazily, ie, only if a() evaluates to true as the destructuring expects?

I know I could chain match/if, but I really wanted to be able to match a pattern this way. It would bring a lot of clarity to the code I'm working in. I'd like to propose this feature.

fn main() {
    if let (true, Some(_)) = (a(), b()) {
        println!("c");
    }
}

fn a() -> bool {
    println!("a");
    return false;
}

fn b() -> Option<i32> {
    println!("b");
    return Some(33);
}

The (a(), b()) value expression must be fully evaluated to construct the tuple.

It isn't clear to me why this optimization should only be allowed for an if let scrutinee, and not match, while let, or let. Or whether it applies to any type or only tuples. How does it cleanly deal with or-patterns? What about @ bindings?

Additionally, you can make it lazy yourself by calling b explicitly whenever you are ready to evaluate it. I'm not entirely convinced that magical laziness is worth the trouble.

if let (true, lazy) = (a(), b) {
    println!("c");
    lazy();
}

Ah, it looks like Stabilize let chains in the 2024 edition by est31 · Pull Request #132833 · rust-lang/rust might be what you want.

if a() && let Some(_) = b() {
    println!("c");
}
7 Likes

Yes, I think it should be allowed to the other ones too, that was just a simple example. Let me bring the code I'm working in, hopefully it will make more sense:

fn example() {
    let mut s = Scanner::new(
        "
        KeyA = ValA
        KeyB = ValB
        KeyC   ValC
        ",
    );

    while s.more() {
        if let (_, Some(k), _, Some(_), _, Some(v)) = (
            s.ws(),
            s.whilef(char::is_alphanumeric),
            s.ws(),
            s.matchs("="),
            s.ws(),
            s.whilef(char::is_alphanumeric),
        ) {
            println!("Key: {}, Val: {}", k, v);
            continue;
        }
        println!("Syntax error {}", s.str());
        break;
    }
}

In this example all the matchers are called, which means it will give the wrong answer when it parses the third line (KeyC ValC -- it will set the cursor after ValC, instead of before). If there was a way to tell to lazily call them it would give the right answer. Since they're not, the correct code will look a lot uglier.

Btw, I think it should allow to access the previous result in the next ones too; pseudocode example:

if let lazy (Some(a), Some(b)) = (req_a(), req_b(a)) {

Oh, the "let chain" thing seems good, but the syntax I proposed is nicer =P, but yeah I don't think this one would deal with Or, but only And.

I find it hard to read the code as written. I have to count tuple fields and count lines to find which line of the expression maps to the Some(_) in question. let-chain makes this so much clearer:

if let _ = s.ws()
    && let Some(k) = s.whilef(char::is_alphanumeric)
    && let _ = s.ws()
    && let Some(_) = s.matchs("=")
    && let _ = s.ws()
    && let Some(v) = s.whilef(char::is_alphanumeric)
{
    println!("Key: {}, Val: {}", k, v);
    continue;
}

It's probably better to use s.ws().is_some() instead of the non-binding pattern, assuming it returns Option. And for that matter, s.matchs("=").is_some()

4 Likes

I agree it's a bit harder to read, but it's already like that if you use tuple destructuring. But thanks for the feedback! I'll look into the let chain.

edit: I was wondering, is it possible to config the formater to put the && in the end of the line and align the "=" of each line in the let-chain? I think it would be a lot nicer to read:

if  let _       = s.ws() &&
    let Some(k) = s.whilef(char::is_alphanumeric) &&
    let _       = s.ws() &&
    let Some(_) = s.matchs("=") &&
    let _       = s.ws() &&
    let Some(v) = s.whilef(char::is_alphanumeric)
{
    println!("Key: {}, Val: {}", k, v);
    continue;
}

This may get stabilized soon:

https://rust-lang.github.io/rfcs/2497-if-let-chains.html

3 Likes

My instinct here would be to reach for the ? operator:

let Some(()) = try {
    while s.more() {
        s.ws();
        let k = s.whilef(char::is_alphanumeric)?;
        s.ws();
        s.matchs("=")?;
        s.ws();
        let v = s.whilef(char::is_alphanumeric)?;

        println!("Key: {}, Val: {}", k, v);
    }
    Ok(())
} else {
    println!("Syntax error {}", s.str());
    return;
}
3 Likes