The Semicolon’s Little Ballet in Happy Rust Programming

Rust is a robust and efficient system programming language. One of the lesser-known heroes contributing to Rust’s elegance is the semicolon, which plays a pivotal role in shaping the language’s expressiveness. Today, let’s delve into the importance of the semicolon in the context of function return values and the last expression of a block.

The Quiet Communicator

The semicolon in Rust is not just a line terminator; it’s a quiet communicator between the developer and the compiler. Expressions followed by a semicolon represent statements, and statements have no return value. On the other hand, expressions without a trailing semicolon are considered the last expression of a block, and their value becomes the return value of the block.

Statements

A statement is a component of a block, which is in turn a component of an outer expression or function. Rust has two kinds of statements: declaration statements and expression statements.

https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/reference/statements.html

Expressions

An expression may have two roles: it always produces a value, and it may have side effects. 

source: https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/reference/expressions.html

Readability

When a semicolon appears at the end of a line, it’s a statement. It’s serving a purpose within the block but not contributing to the return value. Conversely, the absence of a semicolon signals that the expression’s value matters, making it a candidate for the return value.

Consider the following example:

fn is_armstrong_number(num: u32) -> bool {
    let num_str = num.to_string();
    let exponent = num_str.len() as u32;
    let sum: u64 = num_str
        .chars()
        .map(|x| x.to_digit(10).unwrap() as u64)
        .map(|x| x.pow(exponent))
        .sum();
    sum == num as u64  // expression (return value)
}

source: https://gh.com/ls/armstrongNumbers/commit/eee1836#diff-6830e54R15-R24

The absence of the semicolon indicates that the boolean expression is the return value of the function.

fn is_armstrong_number(num: u32) -> bool {
    let num_str = num.to_string();
    let exponent = num_str.len() as u32;
    let sum: u64 = num_str
        .chars()
        .map(|x| x.to_digit(10).unwrap() as u64)
        .map(|x| x.pow(exponent))
        .sum();
    sum == num as u64 // statement
}

Let’s look at the resulting error:

error[E0308]: mismatched types
  –> src/main.rs:34:41
   |
34 | pub fn is_armstrong_number(num: u32) -> bool {
   |        ——————-              ^^^^ expected `bool`, found `()`
   |        |
   |        implicitly returns `()` as its body has no tail or `return` expression

43 |
    sum == num as u64;
   |                      – help: remove this semicolon to return this value
For more information about this error, try `rustc –explain E0308`.
error: could not compile `rust` (bin “rust” test) due to previous error

The semicolon identifies the second last line as a statement that causes the implicit return of `()`.

Conclusion

By distinguishing between Rust statements and expressions, the semicolon fosters a coding style that is not only concise but also expressive.

2 Comments

  1. Good article,

    there is a little (critical) typo in the first code example. The semicolon should be removed:

    sum == num as u32; // expression (return value)

    And in the second example:

    sum == num as u32; // statement

    It should be

    return sum == num as u32; // statement

    The semicolon should be removed.

Leave a Reply to Lothar Schulz Cancel reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.