diff --git a/proposals/README.md b/proposals/README.md index ca3bbc9f2bc9..c4ee46c5db44 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -54,5 +54,6 @@ request: - [0438 - Functions](p0438.md) - [0444 - GitHub Discussions](p0444.md) - [0447 - Generics terminology](p0447.md) +- [0538 - `return` with no argument](p0538.md) diff --git a/proposals/p0538.md b/proposals/p0538.md new file mode 100644 index 000000000000..6431a05f449b --- /dev/null +++ b/proposals/p0538.md @@ -0,0 +1,295 @@ +# `return` with no argument + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/538) + + + +## Table of contents + +- [Problem](#problem) + - [Use cases](#use-cases) + - [What's wrong with the C++ rule?](#whats-wrong-with-the-c-rule) +- [Background](#background) +- [Proposal](#proposal) +- [Details](#details) + - [Relation to upcoming proposals](#relation-to-upcoming-proposals) +- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) +- [Alternatives considered](#alternatives-considered) + - [Retain the C++ rule](#retain-the-c-rule) + - [Fully divorce functions and procedures](#fully-divorce-functions-and-procedures) + + + +## Problem + +### Use cases + +We wish to support the following use cases: + +- Functions that return values. This should be supported for any type for + which a value can be formed, and such support should be uniform and + independent of whether the return type is `()`. A return value must always + be produced. +- Functions that do not return a value ("procedures"). In such functions, we + do not need or want to be able to return a value. No return value is needed, + so if control flow reaches the end of a procedure, it should return to its + caller. +- Functions with parameterized return types, that may be either of the above, + depending on the parameterization. For example, a call wrapper might be + parameterized by the type of its callee, and might return the same type that + its callee returns, or a type computed based on that type. + +### What's wrong with the C++ rule? + +C++ treats `void` as a special case in a number of ways. We want to minimize the +impact of this special-case treatment for the corresponding Carbon types. One +way that this special treatment is visible in C++ is that functions with the +return type `void` obey different rules: the operand of `return` becomes +optional (but is still permitted), and reaching the end of the function becomes +equivalent to `return;`. In a function template, this can even happen invisibly, +with some instantiations having an implicit `return;` and others not. + +This interferes with the ability to reason about programs. Consider: + +``` +template auto f() { + if (T::cond()) + return T::run(); +} +``` + +Here, it is possible for control flow to reach the end of the function. However, +a compiler can't warn on this without false positives, because it's possible +that `T::run()` has type `void`, in which case this function has an implicit +`return;` added before its `}`, and indeed, it might be the case that `T::run()` +always has return type `void` for any `T` where `T::cond()` returns `false`. + +## Background + +See the following issues: + +- [Proposal: function declaration syntax](https://github.com/carbon-language/carbon-lang/pull/438) +- [Proposal: `return` statements](https://github.com/carbon-language/carbon-lang/pull/415) +- [Leads question: what is the relationship between `Void` and `()`?](https://github.com/carbon-language/carbon-lang/issues/443) +- [Leads question: should we allow `return;` in functions with a `Void` return type?](https://github.com/carbon-language/carbon-lang/issues/518) + +## Proposal + +Instead of applying special-case rules based on whether the return type of a +function is `()`, we apply special-case rules based on whether a return type is +provided. + +## Details + +A function with no declared return type is a _procedure_. The return type of a +procedure is implicitly `()`, and a procedure always returns the value `()` if +and when it returns. Inside a procedure, `return` must have no argument, and if +control flow reaches the end of a procedure, the behavior is as if `return;` is +executed. + +``` +// F is a procedure. +fn F() { + if (cond) { + return; + } + if (cond2) { + // Error: cannot return a value from a procedure. + return F(); + } + + // Implicitly `return;` here. +} +``` + +A function with a declared return type is treated uniformly regardless of +whether that return type happens to be `()`. Every `return` statement must +return a value. There is no implicit `return` at the end of the function, and +instead a compile error is produced if control flow can reach the end of the +function, even if the return type is `()` -- or any other unit type. + +``` +fn G() -> () { + if (cond) { + // OK, F() returns (). + return F(); + } + if (cond2) { + // Error: return without value in value-returning function. + return; + } + + // Error: control flow can reach end of value-returning function. +} +``` + +From the caller's perspective, there is no difference between a function +declared as + +``` +fn DoTheThing() -> (); +``` + +and a function declared as + +``` +fn DoTheThing(); +``` + +As a result, the choice to include or omit the `-> ()` in the definition is an +implementation detail, and the syntax used in a forward declaration is not +required to match that used in a definition. The use of `-> ()` in idiomatic +Carbon code is expected to be rare, but it is permitted for uniformity, and in +case there is a reason to desire the value-returning-function rules or to +emphasize to the reader that `()` is the return type or similar. + +### Relation to upcoming proposals + +Issue [#510](https://github.com/carbon-language/carbon-lang/issues/510) asks +whether we should support named return variables: + +``` +fn F() -> var ReturnType: x { + // initialize x + return; +} +``` + +If we do, functions using that syntax should follow the rules for procedures in +this proposal, including the implicit `return;` if control reaches the end of +the function. In particular, + +``` +fn F() { ... } +``` + +would be exactly equivalent to + +``` +fn F() -> var (): _ = () { ... } +``` + +## Rationale based on Carbon's goals + +- **Software and language evolution** + - This proposal may decrease the number of places in which the return type + of a procedure is used, by discouraging the use of + `return Procedure();`. This in turn may make it easier to change a + return type from `()` to something else, but this proposal by itself is + insufficient to ensure that is always possible. +- **Code that is easy to read, understand, and write** + - The conceptual integrity of the Carbon language is improved by making + the same syntax result in the same semantics, regardless of whether a + type happens to be `()` or not, and symmetrically by using different + syntax for different semantics. + - The readability of Carbon code is improved and a source of surprise is + eliminated by removing the possibility of `return F();` being mixed with + `return;` in the same function. +- **Practical safety guarantees and testing mechanisms** + - By making the presence or absence of an implicit `return` in a function + be determined based on syntax alone, we permit checks for missing + `return` statements to be provided in the definition of a template or + generic, without needing to know the arguments. This is important for + generics in particular, because we do not want monomorphization to be + able to fail and because we do not in general guarantee that + monomorphization will be performed. +- **Fast and scalable development** + - The correct syntax for a `return` statement can be detected while + parsing, using only syntactic information rather than contextual, + semantic information. In practice, we will likely parse both kinds of + `return` statement in all functions and check the return type from a + context that has the semantic information, but the ability to do these + checks syntactically may be useful for simple tools and editor + integration. +- **Interoperability with and migration from existing C++ code** + - This proposal rejects some constructs that would be valid in C++: + ``` + return F(); + ``` + in a function with `void` return type would no longer be valid in a + corresponding Carbon function with no specified return type, and would + need to be translated into + ``` + F(); + return; + ``` + (possibly with braces added). However, the fact that this construct is + valid in C++ is surprising to many, and the constructs that would be + idiomatic in C++ are still valid under these rules. + +## Alternatives considered + +### Retain the C++ rule + +The advantages of this approach compared to maintaining the C++ rule are +discussed above. The advantage of maintaining the C++ rule would be that Carbon +is more closely aligned with C++. However, the removed functionality -- +specifically, the ability to return an expression of type `void` from a `void` +returning function -- is still available, albeit with a more verbose syntax, and +the existence of that functionality in C++ is a source of surprise to C++ +programmers. + +### Fully divorce functions and procedures + +We could treat the choice of function with `()` return type versus procedure as +being part of the interface rather than being an implementation detail. + +``` +// F is a procedure. +fn F(); +// F is a function returning (). +fn G() -> (); + +// ... + +// Error, procedure redeclared as a function. +fn F() -> () { + return (); +} + +// Error, function redeclared as a procedure. +fn G() { +} +``` + +Then, we could disallow any use of a procedure call in a context that depends on +its return value, treating a procedure call as a statement rather than as an +expression that can be used as a subexpression or an operand of an operator. + +``` +fn Func() -> (); +fn Proc(); +// OK, x is of type (). +auto x = Func(); +// Error, Proc is a procedure. +auto y = Proc(); +``` + +Advantages: + +- Removes all special treatment of `()` in this context. Procedures no longer + need to say that their return type is implicitly `()` nor that they + implicitly return `()`. +- Adding a return type to a procedure -- converting it to a function -- would + be a non-breaking change. + - But we don't have evidence that this is a common problem. +- Prevents a source of programming error where a returned `()` value is stored + and used. + - But it's not clear that this would be a frequent error, and it would + likely be caught in other ways due to the limited API of `()`. + +Disadvantages: + +- Having distinct notions of function versus procedure would be a surprise for + those coming from C++. +- Supporting `auto x = F();` regardless of whether `F` is a function or + procedure may be important for generic code. +- When migrating C++ code to Carbon, this makes the choice of function versus + procedure load-bearing, as there may be uses that depend on a return value + existing, for example in templates.