Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Early-breaking from Rust's match

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.

like image 323
ljrk Avatar asked Jun 14 '16 14:06

ljrk


People also ask

How does Rust pattern matching work?

Rust has an extremely powerful control flow operator called Match. Pattern matching in Rust works by checking if a place in memory matches a certain pattern. Patterns can be : literal values.

How do you break a loop in Rust?

The break control statement allows us to stop the execution of a loop, break out of its iteration cycle and continue on to any code after it. To use the break control statement, we simply write the break keyword where we want to break out of the loop.

What is match keyword in Rust?

Rust has a keyword, match , that allows you to replace complicated if / else groupings with something more powerful. Check it out: #![allow(unused_variables)] fn main() { let x = 5; match x { 1 => println!(" one"), 2 => println!("

What are patterns in Rust?

Patterns are a special syntax in Rust for matching against the structure of types, both complex and simple. Using patterns in conjunction with match expressions and other constructs gives you more control over a program's control flow.


4 Answers

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.

like image 91
Nick Mosher Avatar answered Oct 14 '22 00:10

Nick Mosher


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.

like image 30
Veedrac Avatar answered Oct 14 '22 01:10

Veedrac


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");
}
like image 7
oli_obk Avatar answered Oct 14 '22 00:10

oli_obk


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. */
        } else { 
             break; 
        }

        // if we reach here, by definition one of these conditions is true: y == 0, or y != 0 and code above has run and completed.
like image 1
watr Avatar answered Oct 14 '22 01:10

watr