# 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)
