Is there a shortcut to unwrap or continue in a loop?
Asked Answered
R

6

24

Consider this:

loop {
    let data = match something() {
        Err(err) => {
            warn!("An error: {}; skipped.", err);
            continue;
        },
        Ok(x) => x
    };

    let data2 = match something_else() {
        Err(err) => {
            warn!("An error: {}; skipped.", err);
            continue;
        },
        Ok(x) => x
    };

    // and so on
}

If I didn't need to assign the ok-value to data, I'd use if let Err(err) = something(), but is there a shortcut to the code above that'd avoid copy-pasting the Err/Ok branches on this, I think, typical scenario? Something like the if let that would also return the ok-value.

Romilda answered 11/4, 2018 at 22:11 Comment(9)
You could get rid of the let data and the continue and then just put "other stuff" inside the Ok(x) => case.Diversity
@Diversity True, but that'll quickly turn into a lot of nested code when there're multiple resultish operations inside the loop. Would writing a macro be a way out?Romilda
"avoid copy-pasting the Err/Ok branches on this" what is copy-pasted here?Lumisterol
You are missing the point of struct like Result, this force code to check the error, if there was something to bypass this, what would be the point ? This construct is so frequent in Rust that ? is used to avoid copy of code. For test only or in main function, you can use .unwrap() or .expect()Brethren
I'm not going to edit the question, but I think the title would be more appropriate as "How do I concisely compose Results?" - It seems like your question isn't specifically about if let, seeing as that it's effectively seeking an alternative to the language construct.Counterman
One way to concisely compose Results is to use one of the comprehension crates like map_for or mdo. (Note: I'm the author of map_for)Boccioni
@Lumisterol added the copy/pasteRomilda
@BHustus true, fixed thatRomilda
@Brethren I'm not bypassing the result or error handling, but trying to avoid the copy-paste of the same error handling logic over and over again. Question title better reflects this now I hope, sorry.Romilda
C
28

While I think that E_net4's answer is probably the best one, I'm adding a macro for posterity in case creating a separate function and early-returning with the ? operator is for some reason undesirable.

Here is a simple skip_fail! macro that continues a containing loop when passed an error:

macro_rules! skip_fail {
    ($res:expr) => {
        match $res {
            Ok(val) => val,
            Err(e) => {
                warn!("An error: {}; skipped.", e);
                continue;
            }
        }
    };
}

This macro can be used as let ok_value = skip_fail!(do_something());

Playground link which uses skip_fail to print out numbers divisible by 1, 2, and 3, and print an error when one of the divisions would truncate.

Again, I believe that using ? in a separate function, and returning an Ok(end_result) if nothing fails, is probably the most idiomatic solution, so if you can use that answer you probably should.

Counterman answered 12/4, 2018 at 21:48 Comment(1)
Thanks a lot, this helps with encapsulating array access.Marylynnmarylynne
C
28

Rust 1.65.0 add let-else statements. So you can write it this way:

    loop {
        let data = something()
        let Ok(ok_data) = data else {
            warn!("skipped.");
            continue;
        };
        // ok_data is available here
        let Ok(data2) = something_else(ok_data) else {
            continue;
        };
        // and so on
    }

But you won't have access to the err variable

Carmel answered 28/11, 2022 at 11:34 Comment(0)
P
13

If you are going to "unwrap or continue" on results often, consider encapsulating that logic in a separate function. With it, you can take advantage of the ? syntax to raise errors out of the function. The loop's flow logic can then be written in a single place (although at this point, you might no longer need the continue).

loop {
    if let Err(err) = do_event() {
        warn!("An error: {}; skipped.", err);
        // continue; // you also don't need this
    }
}

fn do_event() -> Result<(), YourErrorType> {
    let data = do_something()?; // 
    let x = something_more()?;  // error propagation!
    Ok(())
}
Pagurian answered 11/4, 2018 at 22:28 Comment(6)
Worth noting, I believe you could also do this with a closure as well, if you don't want to make an entirely separate function in another part of your source code. Though I don't know how good that would look...Counterman
@BHustus Indeed, but I believe that a separate function is easier to read here. :)Pagurian
that will look ugly if do_event return something you need? and you have to unwrap the result after check error?Hollishollister
Not sure if I understood your questions @Sean, but no, raising errors from a result via ? is fairly clean and idiomatic, as presented. And at the top of the calling chain, a good end user application would have a suitable error reporter, presenting the error in a human readable fashion. Consider looking at the eyre and miette crates for examples of error reporting libraries, and this overview video on error handling in Rust.Pagurian
@E_net4thecandidatesupporter sorry that my question confuse you, I mean, if do_event() has some value to return instead of (), may be some code can demonstrate play.rust-lang.org/…Hollishollister
@Hollishollister If a final outcome is expected outside the loop, then sure, the top level logic can be tweaked to use a match instead of if let. A macro can also be built to do this, as hinted by another answer. However, if the point is merely to continue processing the data inside that loop step, then this pattern expects you to extend that logic inside do_event itself.Pagurian
C
6

If you have to chain multiple Oks together, need to use one Ok's value in the next operation, and don't care about where in the chain the error arises, consider and_then:

loop {
    let outcome = something()
                  .and_then(|a| something_else(a))
                  .and_then(|a| another_thing(a))
                  .and_then(|a| {
                      let b = a + salt;
                      one_more(b)
                  });
    if let Err(e) = outcome {
        warn!("An error: {}; skipped.", e);
    }
}

Where something, something_else, another_thing and one_more all return some form of Result. Even though this example removes the continue statement, and_then effectively emulates it by short-circuiting when the Result is of type Err. All further calls down the line will be skipped over.

You can make this even more concise by using non-closures on the statements that only require one function call:

loop {
    let outcome = something()
                  .and_then(something_else)
                  .and_then(another_thing)
                  .and_then(|a| one_more(a + salt));
    if let Err(e) = outcome {
        warn!("An error: {}; skipped.", e);
    }
}

(Note the lack of parentheses on the functions, which indicates they're being used as callable objects rather than taking their return value)

Counterman answered 12/4, 2018 at 0:53 Comment(0)
V
2

If you're open to using unstable features, you can use a try block for this:

#![feature(try_blocks)]

pub fn something() -> Result<String, String> {
    Err(String::from("Badness"))
}

pub fn something_else() -> Result<String, String> {
    Ok(String::from("Ok"))
}

pub fn main() {
    loop {
        let result: Result<(), String> = try {
            let data = something()?;
            let data2 = something_else()?;
        };
        if let Err(e) = result {
            println!("An error: {}; skipped.", e)
        }
    }
}

As shepmaster mentions in the comments this can be done without any unstable featurtes using a closure which is immediately evaluated (an Immediately Invoked Function Expression, or IIFE for short). This is the modification to the solution by E_net4 proposed by MutantOctopus in that solutions comments.

pub fn something() -> Result<String, String> {
    Err(String::from("Badness"))
}

pub fn something_else() -> Result<String, String> {
    Ok(String::from("Ok"))
}

pub fn main() {
    loop {
        let result: Result<(), String> = (|| {
            let data = something()?;
            let data2 = something_else()?;
            Ok(())
        })();
        if let Err(e) = result {
            println!("An error: {}; skipped.", e)
        }
    }
}
Valerle answered 5/3, 2020 at 0:58 Comment(3)
An IIFE is the stable equivalent, which is effectively the same as pointed out in E_net4's answer.Nereen
@Nereen that's neat. I've added that code to the question. But out of interest what does IIFE stand for?Valerle
Immediately invoked function expressionNereen
P
1

You can use my unwrap_or crate to accomplish this.

You can use it to make nice and clean code:

unwrap_or_ok!(callable(&mut param), _, return);

loop {
    let data = unwrap_ok_or!(something(), err, {
        warn!("An error: {}; skipped.", err);
        continue;
    });

    let data2 = unwrap_ok_or!(somethingElse(), err, {
        warn!("An error: {}; skipped.", err);
        continue;
    });
}
Pyxie answered 15/12, 2021 at 20:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.