20

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.

Yuri Geinish
  • 16,744
  • 6
  • 38
  • 40
  • You could get rid of the `let data` and the `continue` and then just put "other stuff" inside the `Ok(x) =>` case. – sepp2k Apr 11 '18 at 22:13
  • @sepp2k 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? – Yuri Geinish Apr 11 '18 at 22:17
  • "avoid copy-pasting the Err/Ok branches on this" what is copy-pasted here? – mcarton Apr 11 '18 at 22:19
  • 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()` – Stargateur Apr 11 '18 at 23:46
  • 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. – MutantOctopus Apr 12 '18 at 01:08
  • 2
    One way to concisely compose `Result`s is to use one of the comprehension crates like [map_for](https://crates.io/crates/map_for) or [mdo](https://crates.io/crates/mdo). (Note: I'm the author of `map_for`) – Jmb Apr 12 '18 at 06:51
  • @mcarton added the copy/paste – Yuri Geinish Apr 12 '18 at 07:07
  • @BHustus true, fixed that – Yuri Geinish Apr 12 '18 at 07:14
  • @Stargateur 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. – Yuri Geinish Apr 12 '18 at 07:18

6 Answers6

26

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.

MutantOctopus
  • 3,431
  • 4
  • 22
  • 31
17

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

seyed
  • 1,555
  • 1
  • 17
  • 24
12

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(())
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
  • 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... – MutantOctopus Apr 12 '18 at 21:54
  • @BHustus Indeed, but I believe that a separate function is easier to read here. :) – E_net4 Apr 12 '18 at 22:06
  • that will look ugly if do_event return something you need? and you have to unwrap the result after check error? – Sean Oct 13 '21 at 10:38
  • 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](https://www.youtube.com/watch?v=WzC7H83aDZc). – E_net4 Oct 13 '21 at 10:46
  • @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 https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=248ae38826cecc369cb07476753b8a60 – Sean Oct 13 '21 at 13:13
  • 1
    @Sean 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. – E_net4 Oct 13 '21 at 14:30
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)

MutantOctopus
  • 3,431
  • 4
  • 22
  • 31
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)
        }
    }
}
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • [An IIFE is the stable equivalent](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2c6af7747193cfa30c105084d4c3c2d7), which is effectively the same as pointed out in [E_net4's answer](https://stackoverflow.com/a/49785300/155423). – Shepmaster Mar 05 '20 at 01:24
  • @Shepmaster that's neat. I've added that code to the question. But out of interest what does IIFE stand for? – Michael Anderson Mar 05 '20 at 02:57
  • [Immediately invoked function expression](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) – Shepmaster Mar 05 '20 at 02:58
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;
    });
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366