mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
Use the keyword `impls` instead of `is` when writing a `where` constraint that a type variable needs to implement an interface or named constraint. What was previously (provisionally) written: ``` fn Sort[T:! Container where .ElementType is Ordered](x: T*); ``` will now be written: ``` fn Sort[T:! Container where .ElementType impls Ordered](x: T*); ``` --------- Co-authored-by: Geoff Romer <gromer@google.com> Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
169 lines
7.6 KiB
Markdown
169 lines
7.6 KiB
Markdown
# Generics details 7: final impls
|
|
|
|
<!--
|
|
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/983)
|
|
|
|
<!-- toc -->
|
|
|
|
## Table of contents
|
|
|
|
- [Problem](#problem)
|
|
- [Background](#background)
|
|
- [Proposal](#proposal)
|
|
- [Details](#details)
|
|
- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
|
|
- [Alternatives considered](#alternatives-considered)
|
|
- [No `final`, all parameterized impls may be specialized](#no-final-all-parameterized-impls-may-be-specialized)
|
|
- [`final` associated constants instead of `final` impls](#final-associated-constants-instead-of-final-impls)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Problem
|
|
|
|
Allowing an impl to be specialized can lead to higher performance if there are
|
|
parameter values for which a more optimized version can be written. However, not
|
|
all impls will be specialized and there are some benefits when that is known:
|
|
|
|
- The values of associated types can be assumed to come from the impl. In many
|
|
cases this means leaking fewer implementation details into the signature of
|
|
a function using generics.
|
|
- The bodies of functions from the impl could be inlined into the caller even
|
|
when using a more dynamic implementation strategy rather than
|
|
monomorphization.
|
|
|
|
However, not all impls can opt-out of specialization, since this can create
|
|
incompatibilities between unrelated libraries. For example, consider two
|
|
libraries that both import parameterized type `TA` and interface `I`:
|
|
|
|
- Library `LB` that defines type `TB` can define an impl with type structure
|
|
`impl TA(TB, ?) as I`.
|
|
- Library `LC` that defines type `TC` can define an impl with type structure
|
|
`impl TA(?, TC) as I`.
|
|
|
|
Both of these are allowed under
|
|
[Carbon's current orphan rules](/docs/design/generics/details.md#orphan-rule). A
|
|
library `LD` that imports both `LB` and `LC` could then query for the
|
|
implementation of `I` by `TA(TB, TC)` and would use the definition from library
|
|
`LB`, which would be a conflict if library `LC` marked its impl definition as
|
|
not specializable.
|
|
|
|
## Background
|
|
|
|
Rust currently does not support specialization, so for backwards compatibility
|
|
impls are final by default in Rust's specialization proposal.
|
|
|
|
## Proposal
|
|
|
|
We propose that impls can be declared `final`, but only in libraries that must
|
|
be imported by any file that would otherwise be able to define a higher-priority
|
|
impl.
|
|
|
|
## Details
|
|
|
|
Details are in
|
|
[the added `final` impl section to the generics details design document](/docs/design/generics/details.md#final-impl-declarations).
|
|
|
|
## Rationale based on Carbon's goals
|
|
|
|
This proposal supports the following of Carbon's goals:
|
|
|
|
- [Performance-critical software](/docs/project/goals.md#performance-critical-software):
|
|
the ability to inline functions defined in `final` impls will in some cases
|
|
improve performance.
|
|
- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution):
|
|
reducing how much implementation details are exposed in a generic function's
|
|
signature allows that function to evolve.
|
|
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write):
|
|
reducing the list of requirements in a generic function signature is an
|
|
improvement to both readability and writability. Furthermore, `final` impls
|
|
are a tool for making code more predictable. For example, making the
|
|
dereferencing impl for pointers final means it always does the same thing
|
|
and produces a value of the expected type.
|
|
|
|
## Alternatives considered
|
|
|
|
### No `final`, all parameterized impls may be specialized
|
|
|
|
In addition to the [problems listed above](#problem), we ran into problems in
|
|
proposal [#911](https://github.com/carbon-language/carbon-lang/pull/911) trying
|
|
to use the `CommonType` interface to define the type of a conditional
|
|
expression, `if <condition> then <true-result> else <false-result>`. The idea is
|
|
that `CommonType` implementations would specify how to combine the types of the
|
|
`<true-result>` and `<false-result>` expressions using an associated type. In
|
|
generic code, however, there was nothing to guarantee that there wouldn't be a
|
|
specialization that would change the result. As a result, nothing could be
|
|
concluded about the common type if either expression was generic. If at least
|
|
the common type of two equal types was guaranteed, then you could use an
|
|
explicit cast to make sure the types were as expected. Some method of limiting
|
|
specialization was needed.
|
|
|
|
We considered other approaches, such as using the fact that the compiler could
|
|
see all implementations of private interfaces, but that didn't address other use
|
|
cases. For example, we don't want users to be able to customize dereferencing
|
|
pointers for their types so that dereferencing pointers behaves predictably in
|
|
generic and regular code.
|
|
|
|
### `final` associated constants instead of `final` impls
|
|
|
|
We considered allowing developers to mark individual items in an impl as `final`
|
|
instead. This gave developers more control, but we didn't have examples where
|
|
that extra control was needed. It also introduced a number of complexities and
|
|
concerns.
|
|
|
|
The value for a `final let` could be an expression dependent on other associated
|
|
constants which could be `final` or not. Checking that a refining impl adheres
|
|
to that constraint is possible, but subtle and possibly tricky to diagnose
|
|
mistakes clearly.
|
|
|
|
If an impl matches a subset of an impl with a `final let`, how should the
|
|
narrower impl comply with the restriction from the broader?
|
|
|
|
```
|
|
interface A {
|
|
let T:! type;
|
|
}
|
|
|
|
impl [U:! Type] Vector(U) as A {
|
|
final let T:! Type = i32;
|
|
}
|
|
|
|
impl Vector(f32) as A {
|
|
// T has to be `i32` because of the `final let`
|
|
// from the previous impl. What needs to be
|
|
// written here?
|
|
}
|
|
```
|
|
|
|
We considered two different approaches, neither of which was satisfying:
|
|
|
|
- **Restate approach:** It could restate the `let` with a consistent value.
|
|
This does not give any indication that the `let` value is constrained, and
|
|
what impl is introducing that constraint, leading to spooky action at a
|
|
distance. It was unclear to us whether the restated `let` should use `final`
|
|
as well, or maybe some other keyword?
|
|
- **Inheritance approach:** We could have a concept of inheriting from an
|
|
impl, and require that any impl refining an impl with `final` members must
|
|
inherit those values rather than declaring them. Inheritance between impls
|
|
might be a useful feature in its own right, but requires there be some way
|
|
to name the impl being inherited from.
|
|
|
|
Consider two overlapping impls that both use `final let`. The compiler would
|
|
need to validate that they are consistent on their overlap, a source of
|
|
complexity for the user. An impl that overlaps both would have to be consistent
|
|
with both, but would not be able to inherit from both, a problem with using the
|
|
inheritance approach.
|
|
|
|
Ultimately we decided that this approach had a lot of complexity, concerns, and
|
|
edge cases and we could postpone trying to solve these problems until such time
|
|
as we determined there was a need for the greater expressivity of being able to
|
|
mark individual items as `final`. This discussion occurred in:
|
|
|
|
- [Document examining an extended example using specialization](https://docs.google.com/document/d/1w-kRC338Jc1ibTu7Vf0pOlGKdrpumfz63bzUIxEj9jY/edit)
|
|
- [2021-11-29 open discussion](https://docs.google.com/document/d/1YhwNKLxQsWf8NPVaRm9PvgPmSM3PIK_KlD1gpNuUfwY/edit#heading=h.6komy889g3hc)
|
|
- [Carbon's #typesystem channel on Discord](https://discord.com/channels/655572317891461132/708431657849585705/910681126236987495)
|