Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft return docs #620

Merged
merged 10 commits into from
Jul 16, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 143 additions & 6 deletions docs/design/control_flow/return.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,162 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
## Table of contents

- [Overview](#overview)
- [Returning empty tuples](#returning-empty-tuples)
- [`returned var`](#returned-var)
- [`return` and initialization](#return-and-initialization)
- [Relevant proposals](#relevant-proposals)

<!-- tocstop -->

## Overview

The `return` statement ends the flow of execution within a function, returning
execution to the caller. If the function returns a value to the caller, that
value is provided by an expression in the return statement. For example:
The `return` statement ends the flow of execution within a
[function](../functions.md), 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:

```carbon
fn Sum(Int a, Int b) -> Int {
fn Sum(a: Int, b: Int) -> Int {
return a + b;
}
```

> TODO: Flesh out text (currently just overview)
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() -> Int {}` 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:

```carbon
// 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](../templates.md). In order to
be consistent with other explicitly specified return types, `return;` is invalid
in this case. For example:

```carbon
// `-> ()` 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](../variables.md) 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 other expressions is not allowed while a `returned var` is in scope.
For example:

```carbon
fn MakeCircle(radius: Int) -> 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:

```carbon
fn MakePointInArea(Area area, Int preferred_x, Int preferred_y) -> 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:

```carbon
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:

```carbon
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:

```carbon
fn CreateVector(x: Int, y: Int) -> 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
> featuers and therefore they are never "optimizations" or done implicitly or
> optionally.

## Relevant proposals

- [Initial syntax](/proposals/p0415.md)
- [Initialization of memory and variables](/proposals/p0257.md)
- [Syntax: `return`](/proposals/p0415.md)
- [`return` with no argument](/proposals/p0538.md)