Elegant way to chain `Option` or `Result` locally

I usually encountered scenarios where I need to chain Option or Result locally (the function return value is not an Option or Result). Let say we want to perform checked sum on four integers, the usual way I write this procedure is

if let Some(sum) = a.checked_add(b).and_then(|v| v.checked_add(c)).and_then(|v| v.checked_add(d)) {
    // do something with sum
} else {
    // other logic
}

I used to write a lot of code in C#, where the common syntax is using ?, like a.checked_add(b)?.checked_add(c)?.checked_add(d). We can use this in Rust, but it will directly return the None value from the function than from the expression. What I come up with is as follows:

if let Some(sum) = || -> Option<_> { a.checked_add(b)?.checked_add(c)?.checked_add(d) } () {
    // do something with sum
} else {
    // other logic
}

This is still less than ideal, but at least more readable. Is there any other elegant approaches to chain Option or Result locally? I wish we could have some syntax to support chain Option or Result locally without closures. (I also wish the ?? operator for assigning default values for nullable value could be introduced to Rust)

1 Like

This is what try_blocks - The Rust Unstable Book are for:

if let Some(sum) = try { a.checked_add(b)?.checked_add(c)?.checked_add(d)? } {
    // do something with sum
} else {
    // other logic
}

For now the IIFE, like you used there, is a good approach.

3 Likes

You can introduce a variable and insert line breaks to avoid very long lines and improve readability:

let sum = a.checked_add(b)
    .and_then(|s| s.checked_add(c))
    .and_then(|s| s.checked_add(d));

if let Some(sum) = sum {
    ...
} else {
    ...
}

The original line in the C#-like syntax (as well as with the try block) is both already too long and too repetitive for my taste. In a code review, I would ask the author to refactor it regardless. This particular case can be rewritten as:

let sum = [b, c, d]
    .into_iter()
    .fold(
        Some(a),
        |sum, term| sum?.checked_add(term)
    );

This doesn't need unstable features and it doesn't introduce repetition when a new term is added, either.

2 Likes

Thanks! Great to know that! Wish that it can be stabilized soon

A macro can also easily replace the code you show.

Note that fold doesn't short-circuit the way the ? original does. Instead, I'd suggest .try_fold(0, i32::checked_add): https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f7607e3cc1430e661ceef6a13f2b562b

Doing a checked sum is actually even the first example in its documentation :slightly_smiling_face: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_fold

2 Likes

My example is just a very simple example. What I actually need is a mixture of various operators. For repeated pattern it's definitely reasonable to use a macro

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.