4.3. Return
This section defines the rules by which a function body produces its result value.
4.3.1. Core rule: return is not required
A function body is a block as defined in block expressions, and the value of the body's last expression becomes the function's return value.
fn add(x: i32, y: i32) -> i32 {
x + y // returned automatically
}
The return keyword is used only for early exit; returning a single value on the normal path is idiomatically expressed with the last expression.
4.3.2. Explicit return
An explicit return has this grammar:
return_expr ::= "return" expr?
return expr;
return;
return expr; exits the function returning the value of expr; return; exits without a value (that is, returning unit). A return expression is divergent: evaluation leaves the function at that point.
fn early(x: i32) -> i32 {
if x < 0 { return 0; } // early exit
x * 2
}
fn validate(s: str) -> bool {
if s.len() == 0 { return false; }
if s.starts_with("#") { return false; }
true
}
4.3.3. Agreement between return type and body
The return type defined in function declarations must agree on every return path of the body. A return path is the type of the body's last expression and the type of expr in every return expr;.
If these differ, they are joined into a sum type by the branch result rules of the expressions overview, and that sum type must be compatible with the declared return type.
fn maybe_int(cond: bool) -> i32 | str {
if cond { return 42i32; }
"no" // sum of both paths = i32 | str, matches the declaration
}
4.3.4. Omitting the return type
As shown in function declarations, the return type may be omitted, in which case it is inferred from the body.
fn double(x: i32) {
x * 2 // inferred: i32
}
The inferred type is the type of all value paths (return expr and the final expression) when they agree, or their sum when they differ. It is a compile error if a value path is mixed with a path that returns no value, or if the return type depends on itself: a recursive function needs -> T. If no path reaches the end of the body or a return, the inferred type is the diverging type (4.3.7).
4.3.5. Multiple returns: tuples
To return multiple values, use a tuple.
fn divmod(n: i32, d: i32) -> (i32, i32) {
let q = n / d;
let r = n % d;
(q, r)
}
let (quotient, remainder) = divmod(10i32, 3i32);
The tuple return type follows tuple types in type expressions, and the receiver uses tuple destructuring from destructuring assignment.
No separate multiple-return keyword or syntax is introduced; tuples suffice.
4.3.6. Calls that discard the result
When a function result is not used, write:
compute(); // result discarded (expression + ;)
_ = compute(); // intentional discard (explicit)
Both forms discard the result, but _ = ... signals that the user intentionally ignores it. Where a tool or linter warns on ignored results, the _ = ... form acts as an acknowledgement that suppresses the warning.
4.3.7. Divergent functions
Some functions never complete normally: control always leaves through return, throw, an infinite loop and so on.
fn fail_always() throws {
throw ValueError { msg: "fatal" };
}
fn loop_forever() {
loop { }
}
The return type of such a function can hold no value. The standard library provides a diverging type for this position (corresponding to Rust's ! or Never); its exact name is defined in the library specification.
A call to a divergent function is compatible with any type, like a divergent block in block expressions.
let x: i32 = if cond {
42i32
} else {
fail_always() // divergent - OK in an i32 position
};
4.3.8. Position of return
A return expression may appear anywhere in a function body: inside blocks, loops, match arms and so on.
fn find(xs: List<i32>, target: i32) -> i32 | none {
for x in xs {
if x == target { return x; } // returns from the function itself inside the loop
}
none
}
A return nested inside a loop, match, etc. exits the enclosing function itself. To exit only the loop, use break from loops.
4.3.9. When the return value is evaluated
The expr of return expr; is fully evaluated when the return expression is evaluated, and then the function exits.
fn f() -> i32 {
return compute_a() + compute_b(); // both evaluated, then the sum is returned
}
Thus if an expression with side effects is inside return, the side effects happen just before returning.
4.3.10. return and try/catch
A return inside a try body likewise exits the enclosing function. However, if there is a finally body from error control flow, it is always evaluated on the way out.
fn cleanup_aware() -> i32 {
try {
return 42i32; // finally runs first
} finally {
cleanup();
}
}
This function calls cleanup() and then returns 42i32.
4.3.11. return in closures and nested functions
A return in a closure or nested function exits that closure or function itself, not the enclosing function.
fn outer() -> i32 {
let f = |x: i32| -> i32 {
if x < 0 { return 0; } // exits only this closure
x * 2
};
f(-5i32); // f returns 0 from this call
100i32 // outer itself returns 100
}
Details on defining nested functions and closures are in Closures.