R
RUSTY YELLOW PAGES

returnKeyword


Explanation

return exits a function immediately with a given value.

Because Rust is expression-oriented, return is rarely required for the final value of a function — the last expression in the body (with no trailing semicolon) is returned implicitly. return exists for early returns, typically from inside a conditional or loop, where control needs to leave the function before reaching its end.

return; with no value is shorthand for return (); and is only valid when the function's return type is (). return is itself an expression of type ! (never) — it never evaluates to anything at its own call site, because control has already left — which lets it appear in expression position, e.g. let x = if cond { return; } else { 5 };.

Usage examples

Exiting a function early with `return`

fn abs(x: i32) -> i32 {
    if x < 0 {
        return -x; // <- exits the function immediately with `-x`
    }
    x
}

Convention: return is rarely used for a function's final value — the last expression in the body (no trailing ;) is returned implicitly, and idiomatic Rust reserves an explicit return for early exits like the branch above. A trailing return x; is perfectly legal, just unidiomatic.

Handling and propagating errors

Inside a single match arm that needs to diverge from the rest of the function, return is often clearer than restructuring the whole function around ?.

fn parse_temperature(raw: &str) -> Result<f64, String> {
    let value = match raw.trim().parse::<f64>() {
        Ok(v) => v,
        Err(e) => return Err(format!("invalid temperature {raw:?}: {e}")), // <- exits the function early with the error
    };
    Ok(value)
}

When the error needs to be reformatted rather than passed straight through, return inside the failing arm reads more directly than threading a map_err into a ? chain — see the Book's chapter on recoverable errors.

Validating input

A guard clause at the top of a function checks for invalid input and returns immediately, so the rest of the function can assume the value is valid.

fn set_fan_speed(percent: i32) -> Result<(), String> {
    if !(0..=100).contains(&percent) {
        return Err(format!("fan speed {percent}% out of range")); // <- guard clause: bail out before doing any real work
    }
    Ok(())
}

Checking the invalid case first and returning keeps the rest of the function at a single indentation level instead of nesting the valid path inside an if — the Rust Design Patterns favor this guard-clause shape over deep nesting.

Explanation

Embedded support: Full

return behaves identically under #![no_std] — no std dependency. The one thing worth noting is the surrounding context: a #![no_std] binary's fn main() -> ! never returns at all (see loop), but ordinary functions below main — especially driver code — use return for early exits exactly as on a hosted target. It composes naturally with ?: embedded drivers commonly return Result<(), E> from functions that talk to a peripheral over I2C/SPI/UART, and ? is just sugar for an early return Err(...) on failure.

Usage examples

Exiting a driver function early on invalid input

fn set_prescaler(timer: &mut Timer, value: u16) -> Result<(), &'static str> {
    if value == 0 {
        return Err("prescaler must be nonzero"); // <- exits the function immediately with the error
    }
    timer.psc().write(|w| w.psc().bits(value));
    Ok(())
}

Composing with `?` in a Result-returning driver function

fn init_sensor(i2c: &mut I2c) -> Result<(), Error> {
    i2c.write(SENSOR_ADDR, &[REG_CONFIG, CONFIG_DEFAULT])?; // <- `?` returns early on failure, same as an explicit `return`
    Ok(())
}