Why is the semicolon after the return statement optional?

Viewed 1945

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.

2 Answers

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 !.

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.

Related