6

I'd like to iterate over a list of Options. If there's a value in one of them, I want to return an error. Here's a contrived example:

fn test(options: &[Option<u8>]) -> Result<(), &u8> {
    for option in options {
        match option {
            None => (),
            Some(value) => {
                // do some stuff here, so I can't just go
                // Some(value) => return Err(value),
                return Err(value); // this semicolon is optional
            }
        }
    }

    Ok(())
}

Adding another semicolon results in an error, but deleting the semicolon does not.

Why is the semicolon after the return statement optional?

Which form should be used in idiomatic Rust: semicolon or no semicolon? Both are accepted by the compiler and seem to produce the same result.

Rich Apodaca
  • 28,316
  • 16
  • 103
  • 129
  • 2
    FWIW — `if let Some(value) = options.iter().flatten().next() { return Err(value); }` – Shepmaster Jun 29 '20 at 12:27
  • 1
    It looks like your question might be answered by the answers of [What's the difference between using the return statement and omitting the semicolon in Rust?](https://stackoverflow.com/q/59013389/155423); [Are semicolons optional in Rust?](https://stackoverflow.com/q/26665471/155423); [Why is using return as the last statement in a function considered bad style?](https://stackoverflow.com/q/27961879/155423); [Is there any reason to put a semicolon after panic?](https://stackoverflow.com/q/50610000/155423). If not, please **[edit]** your question to explain the differences. – Shepmaster Jun 29 '20 at 12:35
  • 2
    Does this answer your question? [What's the difference between using the return statement and omitting the semicolon in Rust?](https://stackoverflow.com/questions/59013389/whats-the-difference-between-using-the-return-statement-and-omitting-the-semico) – E_net4 Jun 29 '20 at 12:38

2 Answers2

10

Why is the semicolon after the return statement optional?

The crux of the issue is that return is not a statement (by itself) in Rust, it's an expression returning !1.

This means that the idiomatic formatting of your test case is actually:

fn test(options: &[Option<u8>]) -> Result<(), &u8> {
    for option in options {
        match option {
            None => (),
            Some(value) => return Err(value),
        }
    }

    Ok(())
}

Note that I removed the {} around the return expression. The right-hand side of => takes an expression, return Err(value) is an expression, it fits right in, no extra fluff needed.

1 ! denotes the bottom type in programming language theory, a type with no instance, and is used to signal expressions which diverge. It's also known as the NEVER type.


As explained in Are semicolons optional in Rust? expressions can be turned into statements in Rust by adding a ;.

Since the right-hand side of => requires an expression, you cannot directly use return Err(value); (since that's now a statement) but you can use a block expression which happens to contain statements, and possibly a final expression.

The optional ; is therefore a property of the block:

  • A block containing a single statement, no final expression: { return Err(value); }. Its type is ().
  • A block containing just a final expression: { return Err(value) }. Its type is !.
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • What if I want to do some stuff before the return so I can't use Some(value) => return value, ? What would be the idiomatic form in that case? I updated my question. – Rich Apodaca Jun 29 '20 at 13:13
  • @RichApodaca: In that case you'll need a block, and then just run `cargo fmt`. Idiomatic syntax is accepted to be the default of `cargo fmt`. In practice, though, both work and produce the same result, so does it really matter? – Matthieu M. Jun 29 '20 at 14:26
5

The ; can be omitted here because return is an expression that evaluates to the ! type, which is then coerced to the empty tuple (), so all match arms have the same type.

which form should be used in idiomatic Rust?

It's idiomatic to add the ; or to remove the surrounding curly braces. That's also what cargo fmt does.

Aloso
  • 5,123
  • 4
  • 24
  • 41
  • Also, block statements (such as the `for ... { ... }` loop) implicitly have a semicolon after them, so in a sense there *is* a semicolon -- an invisible one. – Lambda Fairy Jun 29 '20 at 12:35
  • Yes, after the `for` loop, but not after the `return` statement. – Aloso Jun 29 '20 at 12:47