Early-breaking from Rust's match
Asked Answered
S

5

21

I want to switch through many possible cases for x and there's one case (here x == 0) where I want to check the result of some additional code to determine what to do next. One possibility is to return early from the match.

I'd use break to do this early-returning in C, but this isn't allowed in Rust. return returns from the parent function (in this case main()) and not from the match only (i.e. the println! at the end isn't run!).

I could just negate the sub-condition (here y == 0) and indent the whole lot of following code -- but I find this ugly and unreadable.

Putting the sub-condition into a match-guard is no option for me since it's simply too big.

Is this possible in Rust or is there a better alternative (except creating another subfunction or other work-arounds)?

Minimal example:

fn main() {
    let x = 1;

    match x {
        1 => {
            let y = 0;
            /*
             * do ev1l stuff to y that I don't want to put into the match-guard
             * as it's simply too much.
             */

            /* break early ... */
            if y == 0 {break;} // > error: `break` outside of loop [E0268]

            assert!(y != 0, "y was 0!");
            /* do other stuff in here. */
        }
        _ => {}
    }

    println!("done matching");
}

I found Mixing matching, mutation, and moves in Rust — is it wrong?

match embraces both imperative and functional styles of programming: you can continue using break statements, assignments, et cetera, rather than being forced to adopt an expression-oriented mindset.

Salmi answered 14/6, 2016 at 14:30 Comment(14)
My thought process while reading the question: Create an else block. Oh, OP disallowed that. Maybe a match guard? Oh, OP disallowed that. Make a function? Oh, OP disallowed that.Monkeypot
My question is specifically whether there's such an equivalent to break since imho it's the most "clean" way to solve this problem. Ofcourse if it's not possible I will refrain to one of the other options but imho these are "overkill" for what I want to do.Salmi
There's no way to do this under your restrictions.Andrade
these are "overkill" for what I want to do. I don't think so, using functions to simplify match guards is a great way to make code more readable (a helpful name for the function being a prerequisite of course). Remember, other people need to read your code after you write it. Other people include future you that forgot what's going on here.Cousingerman
@ker The code is really simple and straight-forward but long. That's why it's imho neither appropriate for a function (it's more readable if it's just sitting there, there's no re-use either) nor for putting it into a match-guard since it's too long. But this is not about code-style but just about it being possible or not. So if WiSaGaN is right I'd accept this as an answer (if he'd make it one)Salmi
@larkey: shortening a long but simple piece of code into a (local) function (maybe even a function-local-function) is definitely a way to increase readability. Using a closure can even get you around the issue of too many argumentsCousingerman
@ker That's your opinion but not to debate here ;)Salmi
That's your opinion true. but not to debate here one should always strife to consider other opinions :)Cousingerman
@key I did so but I just don't want to discuss it any further in this question (possibly on code-review or similar) ;)Salmi
@larkey the blog post you've linked is interesting, but I honestly don't know what it means with that sentence.Monkeypot
@Monkeypot Yep, especially considering it's rather 'official' and recent.Salmi
@larkey "recent" is dubious. It's over a year old, but more importantly it predates Rust 1.0.Monkeypot
@Monkeypot Sure but I think 1yr is still rather recent -- at least one of the more up-to-date resources.Salmi
@larkey predating Rust 1.0 is the important part. 1.0 set the mold for Rust; lots of things before 1.0 no longer make sense.Monkeypot
D
20

This has changed at some point, now you can use labels to break out of any block!

e.g.

fn main() {
    let x = 1;

    match x {
        1 => 'label: {
            let y = 0;

            /* break early ... */
            if y == 0 {
                break 'label;
            }

            assert!(y != 0, "y was 0!");
            /* do other stuff in here. */
        }
        _ => {}
    }

    println!("done matching");
}
Demobilize answered 22/1, 2023 at 12:51 Comment(0)
S
10

Something else you could do is make a "self-executing" closure and use a return statement inside. I don't know whether there are any weird performance characteristics of this but syntactically it's pretty clean.

fn main() {
    let x = 1;

    // This closure is just used to catch the "return" statement.
    (|| {
        match x {
            1 => {
                let y = 0;
                /*
                 * do ev1l stuff to y that I don't want to put into the match-guard
                 * as it's simply too much.
                 */

                /* break early ... */
                if y == 0 { return; } // Ok!

                assert!(y != 0, "y was 0!");
                /* do other stuff in here. */
            }
            _ => {}
        }
    })();

    println!("done matching");
}

Here's a playground link showing it working.

Stratification answered 18/4, 2018 at 20:56 Comment(2)
any weird performance characteristics — there should not be as I expect this to be covered by Rust's "zero-cost abstractions".Monkeypot
Great idea, it reminds me of those javascript self-executing closure tricks to isolate parts of the code !Adelbert
C
9

You can wrap the match into a loop that only runs once and break out of the loop

fn main() {
    let x = 1;

    loop { match x {
        1 => {
            let y = 0;
            /*
             * do ev1l stuff to y that I don't want to put into the match-guard
             * as it's simply too much.
             */

            /* break early ... */
            if y == 0 { break; }

            assert!(y != 0, "y was 0!");
            /* do other stuff in here. */
        }
        _ => {}
    } break; }

    println!("done matching");
}
Cousingerman answered 14/6, 2016 at 15:3 Comment(4)
That's an interesting approach but I think rather obfuscates the idea more than it should and is not exactly an answer to my question. Have an upvote but I cannot accept it ;)Salmi
why doesn't it answer your question? it lets you break out of the match right? im asking because im porting a big C 1000+ line switch statement and this is pretty much how im dealing with breaking out of the middle of a matchRodgerrodgers
@donbright This solution works, but I think it's misleading code. If I came across it in the wild, I would have first assumed that the match is executed multiple times, when this is not the case. It would take me quite a while to figure this out, after which I would almost definitely try to refactor the loop statement out - assuming it to be a relic left by a previous fix.Irwinirwinn
i get it now, thank you Camp bell. if i wasnt porting C i would never use this trick... i'd just rewrite it to make more sense in the first place.Rodgerrodgers
O
7

You could create a macro like

macro_rules! block {
    ($xs:block) => {
        loop { break $xs }
    };
}

and do

match x {
    1 => block!({
        ...
        if y == 0 { break; }
        ...
    })
    _ => {}
}

It's not an amazing solution, but it is semantically meaningful.

Olsewski answered 14/6, 2016 at 18:45 Comment(0)
W
2

The answer is: you can't.

The fact that you are trying to do this is a sign that you are not writing idiomatic Rust.

If you absolutely need to break out of a match, consider putting your match in a function that returns a value... this way you can have good control over borrowing, compared to a simple block of code. Then pass the variable to match to the function that will match inside of it. i.e.

fn match_it(val: i8) -> i8 {
    let mut a: i8 = 0;
    match random_or(0, 1) {
        1 => {
                a = 5;
                // other ev1l stuff
                if a == 6 { return 1 }
             },
        0 => a = -5,
        _ => (),
    };
    
    a //return a
}

Based on your code, though, you could just flip two statements and you would achieve your desired control flow:

        if y == 0 {break;} // > error: `break` outside of loop [E0268]

        assert!(y != 0, "y was 0!");
        /* do other stuff in here. */

turns into:

        if y != 0 {
             assert!(y != 0, "y was 0!");
             /* do other stuff in here. */
        }

        // if we reach here, by definition one of these conditions is true: y == 0, or y != 0 and code above has run and completed.
Wheelhorse answered 17/3, 2022 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.