return
Overview
The return
statement ends the flow of execution within a
function, returning execution to the caller. Its syntax is:
return
[ expression ];
If the function returns a value to the caller, that value is provided by an expression in the return statement. For example:
fn Sum(a: i32, b: i32) -> i32 {
return a + b;
}
When a return type is specified, a function must always return
before
control flow can reach the end of the function body. In other words,
fn DoNothing() -> i32 {}
would be invalid because execution will reach the end
of the function body without returning a value.
Returning empty tuples
Returning an empty tuple ()
is special, and similar to C++'s void
returns.
When a function has no specified return type, its return type is implicitly
()
. return
must not have an expression argument in this case. It also has an
implicit return;
at the end of the function body. For example:
// No return type is specified, so this returns `()` implicitly.
fn MaybeDraw(should_draw: bool) {
if (!should_draw) {
// No expression is passed to `return`.
return;
}
ActuallyDraw();
// There is an implicit `return;` here.
}
When -> ()
is specified in the function signature, the return expression is
required. Omitting -> ()
is encouraged, but specifying it is supported for
generalized code structures, including templates. In order to
be consistent with other explicitly specified return types, return;
is invalid
in this case. For example:
// `-> ()` defines an explicit return value.
fn MaybeDraw(should_draw: bool) -> () {
if (!should_draw) {
// As a consequence, a return value must be passed.
return ();
}
ActuallyDraw();
// The return value must again be explicit.
return ();
}
returned var
Variables may be declared with a returned
statement. Its
syntax is:
returned
var statement
When a variable is marked as returned
, it must be the only returned
value
in-scope.
If a returned var
is returned, the specific syntax return var
must be used.
Returning expressions is not allowed while a returned var
is in scope. For
example:
fn MakeCircle(radius: i32) -> Circle {
returned var c: Circle;
c.radius = radius;
// `return c` would be invalid because `returned` is in use.
return var;
}
If control flow exits the scope of a returned
variable in any way other than
return var
, the returned var
's lifetime ends as normal. When this occurs,
return
may again be used with expressions. For example:
fn MakePointInArea(area: Area, preferred_x: i32, preferred_y: i32) -> Point {
if (preferred_x >= 0 && preferred_y >= 0) {
returned var p: Point = { .x = preferred_x, .y = preferred_y };
if (area.Contains(p)) {
return var;
}
// p's lifetime ends here when `return var` is not reached.
}
return area.RandomPoint();
}
return
and initialization
Consider the following common initialization code:
fn CreateMyObject() -> MyType {
return <expression>;
}
var x: MyType = CreateMyObject();
The <expression>
in the return statement of CreateMyObject
initializes the
variable x
here. There is no copy or similar. It is equivalent to:
var x: MyType = <expression>;
This applies recursively, similar to C++'s guaranteed copy elision.
In the case where additional statements should be run between constructing the
return value and returning, the use of returned var
allows for improved
efficiency because the returned var
can directly use the address of var
declared by the caller. For example, here the returned var vector
in
CreateVector
uses the storage of my_vector
for initialization, avoiding a
copy:
fn CreateVector(x: i32, y: i32) -> Vector {
returned var vector: Vector;
vector.x = x;
vector.y = y;
return var;
}
var my_vector: Vector = CreateVector(1, 2);
As a consequence, returned var
is encouraged because it makes it easier to
avoid copies.
TODO: Have some discussion of RVO and NRVO as they are found in C++ here, and the fact that Carbon provides the essential part of these as first-class features and therefore they are never "optimizations" or done implicitly or optionally.
Alternatives considered
- Implicit or expression returns
- Named return variable in place of a return type
- Retain the C++ rule
- Fully divorce functions and procedures
References
- Proposal #257: Initialization of memory and variables
- Proposal
#415: Syntax:
return
- Proposal
#538:
return
with no argument