Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › Expressions

Rust Semicolons: Why One Can Change an Expression’s Value

By Freedom251 Team · · 10 min read

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

In Rust, a semicolon can turn an expression into a statement, and a statement discards the expression’s value. That is why x + 1 can return an i32 from the end of a function, while x + 1; leaves the function body with the unit value () instead. If the function promises to return an integer, that one character changes a successful return into a type mismatch.

The key is not that Rust skips evaluating code before a semicolon. It evaluates the expression; the semicolon says its value is not the block’s result. Once you know to check whether an expression is final, what type it produces, and whether it is being used for its side effect, Rust’s semicolon rules become much easier to read.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What does a semicolon change in Rust?

Rust expressions produce values. A semicolon commonly places an expression in statement position: the expression still runs, but its result is ignored. The official The Rust Programming Language book explains the distinction this way: “If you add a semicolon to the end of an expression, you turn it into a statement, and it will then not return a value.”

Consider these two functions:

fn add_one(x: i32) -> i32 {
    x + 1
}

fn add_one_broken(x: i32) -> i32 {
    x + 1;
}

In add_one, x + 1 is the final expression in the function body. Its value becomes the value of the body, so it satisfies the declared return type i32. In add_one_broken, the semicolon makes x + 1 an expression statement. Rust evaluates the addition but discards its integer result. There is no later final value, so the body has type (), not i32.

The distinction is therefore about the value available to the surrounding code, not whether the expression executes. An expression used only for an effect can be followed by a semicolon; an expression intended to supply a function’s result must remain the block’s final operand without one.

Why does Rust say “expected ()” or report a type mismatch?

That message often means the block ended without a value of the type its context requires. Inspect the block’s final expression first: a semicolon may have changed it into a statement, leaving the block to produce unit, written ().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rust blocks contain statements followed by an optional final operand. Statements run in sequence; if there is a final operand, it determines the block’s type and value. If there is no final operand, a block that does not diverge has unit type and unit value. This is why a function declared to return i32 cannot finish with an expression statement that has discarded its integer.

For example, a body can intentionally do work and then return a different value:

fn add_one_logged(x: i32) -> i32 {
    println!("adding one");
    x + 1
}

The logging call is a statement: its effect is wanted, not a value for the function to return. The addition is the final operand, so its integer becomes the function result. If you put a semicolon after x + 1 as well, the function has no final integer expression.

When debugging, compare the type required by the surrounding context with the value actually supplied by the block. A return type annotation such as -> i32 sets an expectation; it does not turn a unit-valued body into an integer-valued one. The semicolon is a common cause, but the useful question is always: what is the final value of this block?

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why is the last line of a Rust function missing a semicolon?

A function body is a block, and its final expression can supply the function’s return value. When that is the intent, the final expression normally has no trailing semicolon.

Rust does not require a special return keyword for the last expression in a function. This works because the block itself has the type and value of its final operand:

fn square(n: i32) -> i32 {
    n * n
}

The final multiplication produces an i32, so the block does too. Adding a semicolon discards that result:

fn square_without_result(n: i32) -> i32 {
    n * n;
}

Now the block’s final operand is absent, and its value is (). That does not meet the declared return type. The same rule applies to a final expression in a nested block: if the enclosing code needs the nested block’s value, do not discard it with a semicolon.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Not every function needs a non-unit result. If a function is declared without an explicit return type, its return type is unit by default, so a body consisting of statements and no final value can be appropriate. The semicolon is not inherently an error; it is wrong only when it discards a value the surrounding context requires.

When is a semicolon optional in Rust?

Semicolons are optional only in specific syntactic contexts; omission is not a general permission to end any Rust expression without one. A practical rule is that declarations and ordinary expression statements follow different syntax from block and control-flow expressions.

Declarations and items

A let declaration conventionally ends with a semicolon:

let total = 12;

Item declarations such as fn, struct, and use do not take a trailing semicolon as part of their declaration syntax:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
struct Point {
    x: i32,
    y: i32,
}

use std::fmt;

The braces and declaration form matter; do not infer from the no-semicolon function body rule that all declarations behave the same way.

Expressions without a block

Calls, arithmetic expressions, assignments, and many macro invocations used as statements generally need a semicolon. For instance, a call made for its side effect can end a statement:

println!("ready");

Likewise, an arithmetic expression that is not meant to provide a block’s final value is written as an expression statement with a semicolon. Omitting one from an expression without a block is not generally interchangeable with adding one; Rust’s grammar distinguishes these forms.

Block and control-flow expressions

The Rust Reference distinguishes expression statements that require a semicolon from block expressions for which a semicolon may be optional in that grammar. The optional form applies to expressions with blocks, including forms such as if, match, loop, and an explicit block. However, when one of these is used as a statement without a semicolon, its result must be unit ().

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

That condition is important: “optional” describes what the syntax permits, not whether a non-unit value is silently retained in every statement context. If an if expression is meant to determine the value of a function or another expression, use it as a value-producing expression rather than discard it as a statement.

Extra semicolons

Rust permits extra semicolons between statements. They do not change the meaning of the surrounding statements. This does not make a semicolon after a final value expression harmless: there, it changes the expression into a statement and removes that value from the block result.

How do if expressions return a value?

An if expression can be the final operand of a function body. In that position, the selected branch supplies the value, provided the branches have compatible types.

fn classify(n: i32) -> &'static str {
    if n > 0 {
        "positive"
    } else {
        "non-positive"
    }
}

Each branch ends with a string slice expression and no semicolon. The if expression therefore yields a string slice, which is the function’s final value. A side effect can appear earlier without changing that result:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
fn classify_with_log(n: i32) -> &'static str {
    println!("checking");
    if n > 0 {
        "positive"
    } else {
        "non-positive"
    }
}

The println! statement runs, then the final if expression produces the return value. If a semicolon is placed after the closing brace of that if, its value is discarded. With no later final expression, the function body ends as unit rather than as &'static str.

Inside a branch, the same block rule applies. The expression at the end of the selected branch determines that branch block’s value. A semicolon after the branch’s intended result would discard it, potentially leaving the branch with unit and creating a type mismatch between branches or with the function’s expected return type.

How can you tell whether an expression should have a semicolon?

Decide first whether the expression’s value matters to the next enclosing construct. If it is the intended result of a block, leave it as the final expression; if it is only being run for an effect, make it a statement, usually with a semicolon.

  1. Find the surrounding block. This may be a function body, an explicit block, or a block belonging to a control-flow expression.
  2. Identify the final operand. Ignore earlier statements for the moment and look at the last value-producing expression before the closing brace.
  3. Check the required type. Compare the value’s type with the function return type or with what the surrounding expression expects.
  4. Decide whether the value is used. Keep the final expression un-terminated if its value is meant to flow out of the block. Use a semicolon when the expression is only for its side effect and no value should flow out.
  5. Check the syntax context. For an ordinary expression statement without a block, a semicolon is generally required. Block and control-flow expressions have specific grammar that permits an omitted semicolon in statement position, subject to the unit-type rule.

This is a better habit than deleting every semicolon mentioned in a compiler error. A side-effecting statement may need its semicolon; the fix may instead be to restore a final value expression after it, or to change the function’s intended return type if unit is actually what it should return.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

What is the difference between an expression, a statement, and a block?

An expression computes a value, a statement performs an action in sequence while generally not contributing its expression’s value to the block result, and a block groups these pieces into a larger expression. The final operand is what connects a block to expression-based return behavior.

Form What happens What the enclosing block gets
x + 1 as a final operand The addition is evaluated and its value is retained. The block has the addition’s type and value, here an integer type.
x + 1; as a statement The addition is evaluated, then its value is discarded. It does not supply the block’s result; if no later operand exists, a non-diverging block has type ().
println!("ready"); The call-like macro invocation runs as a statement for its effect. It does not provide a later expression’s value.
if condition { ... } in a value position The selected branch block is evaluated. The if expression can have the selected branch’s value, subject to compatible branch types.

“Statement” should not be read as “nothing happens.” An expression statement can have an important effect, and its expression still evaluates. The distinction is whether its computed result is retained as the value of the surrounding block.

What should you change when a semicolon breaks a function?

Start by deciding what the function is supposed to return. Remove the semicolon only if the expression before it is meant to provide that return value; otherwise, preserve the side-effecting statement and ensure the block has the intended final operand or a unit return type.

If the expression is the intended result

Remove its trailing semicolon and leave it as the last expression in the block. Then verify that its type matches the function’s declared return type. For example, the final x + 1 in a function returning i32 supplies the integer result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If the expression is only for an effect

Keep the semicolon when the value is intentionally discarded, as with a logging call, and put any required return expression afterward. If the function is not meant to return a useful value, make sure a unit return is consistent with its declaration and callers.

If the mismatch is in a control-flow branch

Check the end of every branch block. Branches that are being combined as a value-producing expression need compatible result types. A branch ending with an expression statement may instead produce unit, so removing or retaining a semicolon must be based on whether that branch should yield a value.

What are the main semicolon rules to remember?

  • Rust expressions produce values, while expression statements evaluate expressions and ignore their results.
  • A semicolon commonly turns an expression into an expression statement.
  • A block’s final operand determines its type and value.
  • A non-diverging block with no final operand normally has unit type and value, written ().
  • Semicolons may be omitted in particular block/control-flow statement contexts, but not generally from every expression statement.
  • When a return-type mismatch appears, inspect the final expression and any semicolon immediately after it before changing unrelated code.

Frequently Asked Questions

Is Rust’s unit type the same as void?

Unit is a real Rust type with one value, written (), so it can appear explicitly in type positions. It often serves a role similar to a no-useful-result return, but it is not written as void.

Can I use return instead of leaving off the semicolon?

Yes. A function can use an explicit return value; before its closing brace, including when returning early. The final-expression style is a separate, idiomatic way to provide the function’s result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why does a code formatter keep removing a semicolon?

A formatter follows Rust’s syntax and formatting conventions; a semicolon-free final expression is a normal way for a block to return its value. Formatting does not change the underlying distinction between a value-producing final operand and a statement.

Why do Rust tutorials sometimes show semicolons after calls but not after the last line?

The calls are commonly being used as statements for their side effects, while the last line is often the value the surrounding block should produce. The punctuation reflects those different roles, not a special rule for a particular kind of line.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

ExpressionsProgramming syntaxRustRust programmingStatements

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.