mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 09:41:08 +01:00
Allow `auto` as a return type for functions. Only support functions with one `return` statement for now (open question). This explicitly suggests removing the executable semantics `fn name(args) => expression` syntax, and is motivated by reconciling executable semantics with approved Carbon state. [example](https://github.com/carbon-language/carbon-lang/blob/3d1716f6c692a840d8b4b513ddfb8119432a5150/executable_semantics/testdata/fun_named_params.carbon) This aspect is a decision that may be affected by lambda syntax, but we might also choose to keep lambda syntax and function syntax separate -- I don't think there's enough benefit to providing the alternate function syntax right now. Co-authored-by: Geoff Romer <gromer@google.com> Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
321 lines
11 KiB
Markdown
321 lines
11 KiB
Markdown
# Function return type inference
|
|
|
|
<!--
|
|
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
Exceptions. See /LICENSE for license information.
|
|
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
-->
|
|
|
|
[Pull request](https://github.com/carbon-language/carbon-lang/pull/826)
|
|
|
|
<!-- toc -->
|
|
|
|
## Table of contents
|
|
|
|
- [Problem](#problem)
|
|
- [Background](#background)
|
|
- [Lambdas](#lambdas)
|
|
- [`auto` keyword](#auto-keyword)
|
|
- [`const` qualification](#const-qualification)
|
|
- [Proposal](#proposal)
|
|
- [Details](#details)
|
|
- [Explicit return required](#explicit-return-required)
|
|
- [Executable semantics changes](#executable-semantics-changes)
|
|
- [Disallow direct recursion](#disallow-direct-recursion)
|
|
- [Indirect recursion](#indirect-recursion)
|
|
- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
|
|
- [Open questions](#open-questions)
|
|
- [Multiple returns](#multiple-returns)
|
|
- [`const` qualification](#const-qualification-1)
|
|
- [Alternatives considered](#alternatives-considered)
|
|
- [Only allow `auto` return types if parameters are generic](#only-allow-auto-return-types-if-parameters-are-generic)
|
|
- [Provide alternate function syntax for concise return type inference](#provide-alternate-function-syntax-for-concise-return-type-inference)
|
|
- [Allow separate declaration and definition](#allow-separate-declaration-and-definition)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Problem
|
|
|
|
Should there be a shorter way of declaring a function? This embodies two
|
|
questions:
|
|
|
|
- Should there be a way for a declarer to ask that the return type of a
|
|
function be inferred from returned values?
|
|
- Should there be an alternate function syntax that provides briefer function
|
|
declarations for simple cases of return type inference?
|
|
|
|
## Background
|
|
|
|
Under
|
|
[Proposal #438: Add statement syntax for function declarations](https://github.com/carbon-language/carbon-lang/pull/438),
|
|
the syntax approved was:
|
|
|
|
> `fn` _identifier_ `(` _args_ `)` _[_ `->` _expression ]_ `{` _statements_ `}`
|
|
|
|
Executable semantics currently supports the syntax:
|
|
|
|
> `fn` _identifier_ `(` _args_ `) =>` _expression_
|
|
|
|
In C++, there is similar automatic type inference for return types, although the
|
|
use of `=>` reflects syntax tentatively discussed for matching use.
|
|
|
|
### Lambdas
|
|
|
|
Lambdas are also under discussion for Carbon: however, a different syntax is
|
|
likely to arise. This proposal does not try to address lambda syntax, although
|
|
it is possible that a decision on lambda syntax may lead to an alternate syntax
|
|
for declaring functions in order to maintain syntax parity.
|
|
|
|
### `auto` keyword
|
|
|
|
This is the first proposal to formally include a use of `auto`; although
|
|
[`var` statement #339](p0339.md) mentions it in an alternative, the proposal did
|
|
not explicitly propose the keyword. However, the `var x: auto` use-case is
|
|
expected in Carbon, and so uses of `auto` here can be considered in that
|
|
context.
|
|
|
|
Note the `auto` keyword name and behavior can generally be considered consistent
|
|
with C++.
|
|
|
|
### `const` qualification
|
|
|
|
C++ initially used the matching return type for lambdas. However, it switched to
|
|
its `auto` variable rules, which are defined in terms of template argument
|
|
deduction and discard `const` qualifiers. This yields subtle behavioral
|
|
differences.
|
|
|
|
Note that `const` is not yet defined in Carbon.
|
|
|
|
## Proposal
|
|
|
|
Support automatic type inference in Carbon with a new `auto` keyword, as in:
|
|
|
|
> `fn` _identifier_ `(` _args_ `) -> auto {` _statements_ `}`
|
|
|
|
For now, only one return statement is allowed: we will avoid defining rules for
|
|
handling differing return types.
|
|
|
|
Separate declarations and definitions are not supported with `auto` return types
|
|
because the declaration must have a known return type.
|
|
|
|
## Details
|
|
|
|
### Explicit return required
|
|
|
|
Functions using `-> auto` must return an expression; using the implicit return
|
|
or `return;` is invalid. This is consistent with the
|
|
[design for returning empty tuples](/docs/design/control_flow/return.md#returning-empty-tuples).
|
|
|
|
### Executable semantics changes
|
|
|
|
Executable semantics will remove the `=>` function syntax.
|
|
|
|
In practice, this means the current executable semantics syntax:
|
|
|
|
```
|
|
fn Add(x: i32, y: i32) => x + y;
|
|
```
|
|
|
|
Becomes:
|
|
|
|
```
|
|
fn Add(x: i32, y: i32) -> auto { return x + y; }
|
|
```
|
|
|
|
### Disallow direct recursion
|
|
|
|
Direct recursive calls can create complexities in inferring the return type. For
|
|
example:
|
|
|
|
```
|
|
// In the return statement.
|
|
fn Factorial(x: i32) -> auto {
|
|
return (if x == 1 then x else x * Factorial(x - 1));
|
|
}
|
|
|
|
// Before the return statement, but affecting the return type.
|
|
fn Factorial(x: i32) -> auto {
|
|
var x: auto = (if x == 1 then x else x * Factorial(x - 1));
|
|
return x;
|
|
}
|
|
```
|
|
|
|
As a consequence, direct recursion is rejected: that is, a function with an
|
|
`auto` return type may not call itself.
|
|
|
|
#### Indirect recursion
|
|
|
|
Indirect recursion will typically look like:
|
|
|
|
```
|
|
fn ExplicitReturn() -> i32;
|
|
fn AutoReturn() -> auto { return ExplicitReturn(); }
|
|
fn ExplicitReturn() -> i32 { return AutoReturn(); }
|
|
```
|
|
|
|
This is valid because the return type of `AutoReturn()` can be calculated to be
|
|
`i32` from the forward declaration of `ExplicitReturn`.
|
|
|
|
Due to how name lookup works, there is no risk of indirect recursion causing
|
|
problems for typing:
|
|
|
|
- A similar separate declaration of an `auto` return is
|
|
[disallowed](#proposal).
|
|
- Without a separate declaration, name lookup would fail. That is, the below
|
|
has a name lookup error at `return B();`:
|
|
|
|
```
|
|
fn A() -> auto { return B(); }
|
|
fn B() -> auto { return A(); }
|
|
```
|
|
|
|
As a consequence, no specific rules about indirect recursion are needed, unlike
|
|
direct recursion.
|
|
|
|
## Rationale based on Carbon's goals
|
|
|
|
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
|
|
|
|
- We are avoiding providing an alternative function syntax in order to
|
|
provide users less syntax they'll need to understand.
|
|
- As a practical measure, there are likely to be cases in generic code
|
|
that are more feasible to write.
|
|
|
|
- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
|
|
|
|
- The intent is to have `auto` as a return type work similarly to C++'s
|
|
type inference, in order to ease migration.
|
|
|
|
## Open questions
|
|
|
|
### Multiple returns
|
|
|
|
It's likely that we should support `auto` on functions with multiple returns.
|
|
This proposal declines to address that use-case because of the complexities
|
|
which arise when return types may differ. Instead, the use-case is left for
|
|
future proposals to handle.
|
|
|
|
### `const` qualification
|
|
|
|
As noted in background, [`const` qualification](#const-qualification) has seen
|
|
changes in C++. While we should work to choose a story that can remain
|
|
long-term, this will likely be revisited when rules around deduction for
|
|
templates are addressed.
|
|
|
|
## Alternatives considered
|
|
|
|
### Only allow `auto` return types if parameters are generic
|
|
|
|
`auto` return types are likely to be a negative impact to readability, because a
|
|
reader must read the function body in order to determine the return type. We
|
|
could detect whether parameters used in determining an `auto` return type are
|
|
generic, and only allow `auto` to be used when they are.
|
|
|
|
Advantages:
|
|
|
|
- Limits the readability impact of `auto`.
|
|
- `auto` could not be used where the return type is clearly deterministic,
|
|
and thus easy to write. In these cases, readers would never need to look
|
|
at the function body to determine the return.
|
|
- `auto` could be used where the return type may be difficult to easily
|
|
write as a consequence of potential generic parameters.
|
|
|
|
Disadvantages:
|
|
|
|
- Increases the rule set around use of `auto` in return types.
|
|
- Breaks C++ compatibility.
|
|
|
|
The decision to allow `auto` in more cases is primarily for C++ compatibility.
|
|
|
|
### Provide alternate function syntax for concise return type inference
|
|
|
|
Executable semantics currently demonstrates an alternate function syntax with:
|
|
|
|
```
|
|
fn Add(x: i32, y: i32) => x + y;
|
|
```
|
|
|
|
This is a more concise syntax that builds upon return type inference. We could
|
|
keep that, or come up with some other syntax.
|
|
|
|
Advantages:
|
|
|
|
- For short functions, provides a more succinct manner of defining the
|
|
function.
|
|
- `=>` use echoes tentative matching syntax.
|
|
|
|
Disadvantages:
|
|
|
|
- Creates an additional syntax which can be used for equivalent behavior.
|
|
- Overlaps with lambda use-cases, but lambdas will likely end up looking
|
|
different; that is, we should not assume the syntax will converge.
|
|
- We may in particular take a more sharply divergent syntax for lambdas,
|
|
in order to allow for significant brevity in typing, which may not make
|
|
sense to support for function declarations.
|
|
- Limits use of `=>` for other purposes.
|
|
- To the extent that `=>` might primarily be replacing `-> auto`, assuming
|
|
Carbon adopted Rust-style
|
|
[block expression returns](https://doc.rust-lang.org/reference/expressions/block-expr.html),
|
|
this offers limited value for the additional token use.
|
|
|
|
This proposal suggests not adding an inference-focused alternate function syntax
|
|
at this time; while consistent with tentative matching syntax, the split from
|
|
function syntax is significant. The
|
|
[one way principle](/docs/project/principles/one_way.md) applies here.
|
|
|
|
If an alternate function syntax is added, it should be done in tandem with a
|
|
lambda proposal in order to ensure consistency in syntax.
|
|
|
|
### Allow separate declaration and definition
|
|
|
|
The return type must be possible to determine from the declaration. As a
|
|
consequence, the declaration and definition of a function returning `auto`
|
|
cannot be significantly separated: a caller must be able to see the definition.
|
|
Thus, although a separate declaration would be insufficient for a call, we could
|
|
allow using `auto` with a separate declaration and definition when the caller
|
|
can see _both_.
|
|
|
|
This example would be valid because `CallAdd` can see the `Add` definition:
|
|
|
|
```
|
|
fn Add(x: i32, y: i32) -> auto;
|
|
|
|
fn Add(x: i32, y: i32) -> auto { return x + y; }
|
|
|
|
fn CallAdd() -> i32 { return Add(1, 2); }
|
|
```
|
|
|
|
However, the following example would be invalid because `CallAdd` can only see
|
|
the `Add` declaration (even though the definition may be in the same file), and
|
|
it would need the definition to determine the return type:
|
|
|
|
```
|
|
fn Add(x: i32, y: i32) -> auto;
|
|
|
|
fn CallAdd() -> i32 { return Add(1, 2); }
|
|
|
|
fn Add(x: i32, y: i32) -> auto { return x + y; }
|
|
```
|
|
|
|
Advantages:
|
|
|
|
- Separating the declaration and definition could be used in files to cluster
|
|
brief declarations, then put definitions below.
|
|
- A particularly common use-case of this might be in class declarations,
|
|
where a user might want to offer a declaration and define it out of line
|
|
where it doesn't interrupt the class's API.
|
|
- It may be preferred to only use `auto` return types with short
|
|
functions, which limits this advantage as a short function could easily
|
|
be inlined.
|
|
|
|
Disadvantages:
|
|
|
|
- Cannot be used to break call cycles, which is the usual use of separate
|
|
declarations and definitions.
|
|
- A separate declaration still cannot be called until after the definition
|
|
is provided.
|
|
- This may be particularly confusing to humans.
|
|
|
|
The decision is to not support separate declarations and definitions with `auto`
|
|
return types due to the limited utility.
|