Files
carbon-lang/docs/design/generics/details.md
T
642fcd3b77 Replace keyword is with impls (#2483)
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>
2023-03-22 21:45:32 -07:00

5864 lines
210 KiB
Markdown

# Generics: Details
<!--
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
-->
<!-- toc -->
## Table of contents
- [Overview](#overview)
- [Interfaces](#interfaces)
- [Implementing interfaces](#implementing-interfaces)
- [Implementing multiple interfaces](#implementing-multiple-interfaces)
- [External impl](#external-impl)
- [Qualified member names and compound member access](#qualified-member-names-and-compound-member-access)
- [Access](#access)
- [Generics](#generics)
- [Return type](#return-type)
- [Implementation model](#implementation-model)
- [Interfaces recap](#interfaces-recap)
- [Type-of-types](#type-of-types)
- [Named constraints](#named-constraints)
- [Subtyping between type-of-types](#subtyping-between-type-of-types)
- [Combining interfaces by anding type-of-types](#combining-interfaces-by-anding-type-of-types)
- [Interface requiring other interfaces](#interface-requiring-other-interfaces)
- [Interface extension](#interface-extension)
- [`extends` and `impl` with named constraints](#extends-and-impl-with-named-constraints)
- [Diamond dependency issue](#diamond-dependency-issue)
- [Use case: overload resolution](#use-case-overload-resolution)
- [Adapting types](#adapting-types)
- [Adapter compatibility](#adapter-compatibility)
- [Extending adapter](#extending-adapter)
- [Use case: Using independent libraries together](#use-case-using-independent-libraries-together)
- [Use case: Defining an impl for use by other types](#use-case-defining-an-impl-for-use-by-other-types)
- [Use case: Private impl](#use-case-private-impl)
- [Use case: Accessing external names](#use-case-accessing-external-names)
- [Adapter with stricter invariants](#adapter-with-stricter-invariants)
- [Associated constants](#associated-constants)
- [Associated class functions](#associated-class-functions)
- [Associated types](#associated-types)
- [Implementation model](#implementation-model-1)
- [Parameterized interfaces](#parameterized-interfaces)
- [Impl lookup](#impl-lookup)
- [Parameterized named constraints](#parameterized-named-constraints)
- [Where constraints](#where-constraints)
- [Constraint use cases](#constraint-use-cases)
- [Set an associated constant to a specific value](#set-an-associated-constant-to-a-specific-value)
- [Same type constraints](#same-type-constraints)
- [Set an associated type to a specific value](#set-an-associated-type-to-a-specific-value)
- [Equal generic types](#equal-generic-types)
- [Satisfying both type-of-types](#satisfying-both-type-of-types)
- [Type bound for associated type](#type-bound-for-associated-type)
- [Type bounds on associated types in declarations](#type-bounds-on-associated-types-in-declarations)
- [Type bounds on associated types in interfaces](#type-bounds-on-associated-types-in-interfaces)
- [Combining constraints](#combining-constraints)
- [Recursive constraints](#recursive-constraints)
- [Parameterized type implements interface](#parameterized-type-implements-interface)
- [Another type implements parameterized interface](#another-type-implements-parameterized-interface)
- [Constraints must use a designator](#constraints-must-use-a-designator)
- [Implied constraints](#implied-constraints)
- [Must be legal type argument constraints](#must-be-legal-type-argument-constraints)
- [Referencing names in the interface being defined](#referencing-names-in-the-interface-being-defined)
- [Manual type equality](#manual-type-equality)
- [`observe` declarations](#observe-declarations)
- [Other constraints as type-of-types](#other-constraints-as-type-of-types)
- [Is a derived class](#is-a-derived-class)
- [Type compatible with another type](#type-compatible-with-another-type)
- [Same implementation restriction](#same-implementation-restriction)
- [Example: Multiple implementations of the same interface](#example-multiple-implementations-of-the-same-interface)
- [Example: Creating an impl out of other implementations](#example-creating-an-impl-out-of-other-implementations)
- [Sized types and type-of-types](#sized-types-and-type-of-types)
- [Implementation model](#implementation-model-2)
- [`TypeId`](#typeid)
- [Destructor constraints](#destructor-constraints)
- [Generic `let`](#generic-let)
- [Parameterized impl declarations](#parameterized-impl-declarations)
- [Impl for a parameterized type](#impl-for-a-parameterized-type)
- [Conditional conformance](#conditional-conformance)
- [Conditional methods](#conditional-methods)
- [Blanket impl declarations](#blanket-impl-declarations)
- [Difference between a blanket impl and a named constraint](#difference-between-a-blanket-impl-and-a-named-constraint)
- [Wildcard impl declarations](#wildcard-impl-declarations)
- [Combinations](#combinations)
- [Lookup resolution and specialization](#lookup-resolution-and-specialization)
- [Type structure of an impl declaration](#type-structure-of-an-impl-declaration)
- [Orphan rule](#orphan-rule)
- [Overlap rule](#overlap-rule)
- [Prioritization rule](#prioritization-rule)
- [Acyclic rule](#acyclic-rule)
- [Termination rule](#termination-rule)
- [`final` impl declarations](#final-impl-declarations)
- [Libraries that can contain a `final` impl](#libraries-that-can-contain-a-final-impl)
- [Comparison to Rust](#comparison-to-rust)
- [Forward declarations and cyclic references](#forward-declarations-and-cyclic-references)
- [Declaring interfaces and named constraints](#declaring-interfaces-and-named-constraints)
- [Declaring implementations](#declaring-implementations)
- [Matching and agreeing](#matching-and-agreeing)
- [Declaration examples](#declaration-examples)
- [Example of declaring interfaces with cyclic references](#example-of-declaring-interfaces-with-cyclic-references)
- [Interfaces with parameters constrained by the same interface](#interfaces-with-parameters-constrained-by-the-same-interface)
- [Interface members with definitions](#interface-members-with-definitions)
- [Interface defaults](#interface-defaults)
- [`final` members](#final-members)
- [Interface requiring other interfaces revisited](#interface-requiring-other-interfaces-revisited)
- [Requirements with `where` constraints](#requirements-with-where-constraints)
- [Observing a type implements an interface](#observing-a-type-implements-an-interface)
- [Observing interface requirements](#observing-interface-requirements)
- [Observing blanket impl declarations](#observing-blanket-impl-declarations)
- [Operator overloading](#operator-overloading)
- [Binary operators](#binary-operators)
- [`like` operator for implicit conversions](#like-operator-for-implicit-conversions)
- [Parameterized types](#parameterized-types)
- [Specialization](#specialization)
- [Future work](#future-work)
- [Dynamic types](#dynamic-types)
- [Runtime type parameters](#runtime-type-parameters)
- [Runtime type fields](#runtime-type-fields)
- [Abstract return types](#abstract-return-types)
- [Evolution](#evolution)
- [Testing](#testing)
- [Impl with state](#impl-with-state)
- [Generic associated types and higher-ranked types](#generic-associated-types-and-higher-ranked-types)
- [Generic associated types](#generic-associated-types)
- [Higher-ranked types](#higher-ranked-types)
- [Field requirements](#field-requirements)
- [Bridge for C++ customization points](#bridge-for-c-customization-points)
- [Variadic arguments](#variadic-arguments)
- [Range constraints on generic integers](#range-constraints-on-generic-integers)
- [References](#references)
<!-- tocstop -->
## Overview
This document goes into the details of the design of generic type parameters.
Imagine we want to write a function parameterized by a type argument. Maybe our
function is `PrintToStdout` and let's say we want to operate on values that have
a type for which we have an implementation of the `ConvertibleToString`
interface. The `ConvertibleToString` interface has a `ToString` method returning
a string. To do this, we give the `PrintToStdout` function two parameters: one
is the value to print, let's call that `val`, the other is the type of that
value, let's call that `T`. The type of `val` is `T`, what is the type of `T`?
Well, since we want to let `T` be any type implementing the
`ConvertibleToString` interface, we express that in the "interfaces are
type-of-types" model by saying the type of `T` is `ConvertibleToString`.
Since we can figure out `T` from the type of `val`, we don't need the caller to
pass in `T` explicitly, so it can be a
[deduced parameter](terminology.md#deduced-parameter) (also see
[deduced parameters](overview.md#deduced-parameters) in the Generics overview
doc). Basically, the user passes in a value for `val`, and the type of `val`
determines `T`. `T` still gets passed into the function though, and it plays an
important role -- it defines the key used to look up interface implementations.
We can think of the interface as defining a struct type whose members are
function pointers, and an implementation of an interface as a value of that
struct with actual function pointer values. An implementation is a table mapping
the interface's functions to function pointers. For more on this, see
[the implementation model section](#implementation-model).
In addition to function pointer members, interfaces can include any constants
that belong to a type. For example, the
[type's size](#sized-types-and-type-of-types) (represented by an integer
constant member of the type) could be a member of an interface and its
implementation. There are a few cases why we would include another interface
implementation as a member:
- [associated types](#associated-types)
- [type parameters](#parameterized-interfaces)
- [interface requirements](#interface-requiring-other-interfaces)
The function expresses that the type argument is passed in
[statically](terminology.md#static-dispatch-witness-table), basically generating
a separate function body for every different type passed in, by using the
"generic argument" syntax `:!`, see [the generics section](#generics) below. The
interface contains enough information to
[type and definition check](terminology.md#complete-definition-checking) the
function body -- you can only call functions defined in the interface in the
function body. Contrast this with making the type a template argument, where you
could just use `type` instead of an interface and it will work as long as the
function is only called with types that allow the definition of the function to
compile. The interface bound has other benefits:
- allows the compiler to deliver clearer error messages,
- documents expectations, and
- expresses that a type has certain semantics beyond what is captured in its
member function names and signatures.
The last piece of the puzzle is calling the function. For a value of type `Song`
to be printed using the `PrintToStdout` function, `Song` needs to implement the
`ConvertibleToString` interface. Interface implementations will usually be
defined either with the type or with the interface. They may also be defined
somewhere else as long as Carbon can be guaranteed to see the definition when
needed. For more on this, see
[the implementing interfaces section](#implementing-interfaces) below.
Unless the implementation of `ConvertibleToString` for `Song` is defined as
`external`, every member of `ConvertibleToString` is also a member of `Song`.
This includes members of `ConvertibleToString` that are not explicitly named in
the `impl` definition but have defaults. Whether the implementation is defined
as [internal](terminology.md#internal-impl) or
[external](terminology.md#external-impl), you may access the `ToString` function
for a `Song` value `s` by a writing function call
[using a qualified member access expression](terminology.md#qualified-member-access-expression),
like `s.(ConvertibleToString.ToString)()`.
If `Song` doesn't implement an interface or we would like to use a different
implementation of that interface, we can define another type that also has the
same data representation as `Song` that has whatever different interface
implementations we want. However, Carbon won't implicitly convert to that other
type, the user will have to explicitly cast to that type in order to select
those alternate implementations. For more on this, see
[the adapting type section](#adapting-types) below.
## Interfaces
An [interface](terminology.md#interface), defines an API that a given type can
implement. For example, an interface capturing a linear-algebra vector API might
have two methods:
```
interface Vector {
// Here the `Self` keyword means
// "the type implementing this interface".
fn Add[self: Self](b: Self) -> Self;
fn Scale[self: Self](v: f64) -> Self;
}
```
The syntax here is to match
[how the same members would be defined in a type](/docs/design/classes.md#methods).
Each declaration in the interface defines an
[associated entity](terminology.md#associated-entity). In this example, `Vector`
has two associated methods, `Add` and `Scale`.
An interface defines a type-of-type, that is a type whose values are types. The
values of an interface are any types implementing the interface, and so provide
definitions for all the functions (and other members) declared in the interface.
## Implementing interfaces
Carbon interfaces are ["nominal"](terminology.md#nominal-interfaces), which
means that types explicitly describe how they implement interfaces. An
["impl"](terminology.md#impl-implementation-of-an-interface) defines how one
interface is implemented for a type. Every associated entity is given a
definition. Different types satisfying `Vector` can have different definitions
for `Add` and `Scale`, so we say their definitions are _associated_ with what
type is implementing `Vector`. The `impl` defines what is associated with the
type for that interface.
An impl may be defined inline inside the type definition:
```
class Point {
var x: f64;
var y: f64;
impl as Vector {
// In this scope, the `Self` keyword is an
// alias for `Point`.
fn Add[self: Self](b: Self) -> Self {
return {.x = a.x + b.x, .y = a.y + b.y};
}
fn Scale[self: Self](v: f64) -> Self {
return {.x = a.x * v, .y = a.y * v};
}
}
}
```
Interfaces that are implemented inline contribute to the type's API:
```
var p1: Point = {.x = 1.0, .y = 2.0};
var p2: Point = {.x = 2.0, .y = 4.0};
Assert(p1.Scale(2.0) == p2);
Assert(p1.Add(p1) == p2);
```
**Note:** A type may implement any number of different interfaces, but may
provide at most one implementation of any single interface. This makes the act
of selecting an implementation of an interface for a type unambiguous throughout
the whole program.
**Comparison with other languages:** Rust defines implementations lexically
outside of the `class` definition. This Carbon approach means that a type's API
is described by declarations inside the `class` definition and doesn't change
afterwards.
**References:** This interface implementation syntax was accepted in
[proposal #553](https://github.com/carbon-language/carbon-lang/pull/553). In
particular, see
[the alternatives considered](/proposals/p0553.md#interface-implementation-syntax).
### Implementing multiple interfaces
To implement more than one interface when defining a type, simply include an
`impl` block per interface.
```
class Point {
var x: f64;
var y: f64;
impl as Vector {
fn Add[self: Self](b: Self) -> Self { ... }
fn Scale[self: Self](v: f64) -> Self { ... }
}
impl as Drawable {
fn Draw[self: Self]() { ... }
}
}
```
In this case, all the functions `Add`, `Scale`, and `Draw` end up a part of the
API for `Point`. This means you can't implement two interfaces that have a name
in common (unless you use an `external impl` for one or both, as described
below).
```
class GameBoard {
impl as Drawable {
fn Draw[self: Self]() { ... }
}
impl as EndOfGame {
// ❌ Error: `GameBoard` has two methods named
// `Draw` with the same signature.
fn Draw[self: Self]() { ... }
fn Winner[self: Self](player: i32) { ... }
}
}
```
**Open question:** Should we have some syntax for the case where you want both
names to be given the same implementation? It seems like that might be a common
case, but we won't really know if this is an important case until we get more
experience.
```
class Player {
var name: String;
impl as Icon {
fn Name[self: Self]() -> String { return self.name; }
// ...
}
impl as GameUnit {
// Possible syntax options for defining
// `GameUnit.Name` as the same as `Icon.Name`:
alias Name = Icon.Name;
fn Name[self: Self]() -> String = Icon.Name;
// ...
}
}
```
### External impl
Interfaces may also be implemented for a type
[externally](terminology.md#external-impl), by using the `external impl`
construct. An external impl does not add the interface's methods to the type.
```
class Point2 {
var x: f64;
var y: f64;
external impl as Vector {
// In this scope, the `Self` keyword is an
// alias for `Point2`.
fn Add[self: Self](b: Self) -> Self {
return {.x = a.x + b.x, .y = a.y + b.y};
}
fn Scale[self: Self](v: f64) -> Self {
return {.x = a.x * v, .y = a.y * v};
}
}
}
var a: Point2 = {.x = 1.0, .y = 2.0};
// `a` does *not* have `Add` and `Scale` methods:
// ❌ Error: a.Add(a.Scale(2.0));
```
An external impl may be defined out-of-line, by including the name of the
existing type before `as`, which is otherwise optional:
```
class Point3 {
var x: f64;
var y: f64;
}
external impl Point3 as Vector {
// In this scope, the `Self` keyword is an
// alias for `Point3`.
fn Add[self: Self](b: Self) -> Self {
return {.x = a.x + b.x, .y = a.y + b.y};
}
fn Scale[self: Self](v: f64) -> Self {
return {.x = a.x * v, .y = a.y * v};
}
}
var a: Point3 = {.x = 1.0, .y = 2.0};
// `a` does *not* have `Add` and `Scale` methods:
// ❌ Error: a.Add(a.Scale(2.0));
```
**References:** The external interface implementation syntax was decided in
[proposal #553](https://github.com/carbon-language/carbon-lang/pull/553). In
particular, see
[the alternatives considered](/proposals/p0553.md#interface-implementation-syntax).
The `external impl` statement is allowed to be defined in a different library
from `Point3`, restricted by [the coherence/orphan rules](#impl-lookup) that
ensure that the implementation of an interface can't change based on imports. In
particular, the `external impl` statement is allowed in the library defining the
interface (`Vector` in this case) in addition to the library that defines the
type (`Point3` here). This (at least partially) addresses
[the expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions).
Carbon requires `impl` declarations in a different library to be `external` so
that the API of `Point3` doesn't change based on what is imported. It would be
particularly bad if two different libraries implemented interfaces with
conflicting names that both affected the API of a single type. As a consequence
of this restriction, you can find all the names of direct members (those
available by [simple member access](terminology.md#simple-member-access)) of a
type in the definition of that type. The only thing that may be in another
library is an `impl` of an interface.
You might also use `external impl` to implement an interface for a type to avoid
cluttering the API of that type, for example to avoid a name collision. A syntax
for reusing method implementations allows us to do this selectively when needed.
In this case, the `external impl` may be declared lexically inside the class
scope.
```
class Point4a {
var x: f64;
var y: f64;
fn Add[self: Self](b: Self) -> Self {
return {.x = self.x + b.x, .y = self.y + b.y};
}
external impl as Vector {
alias Add = Point4a.Add; // Syntax TBD
fn Scale[self: Self](v: f64) -> Self {
return {.x = self.x * v, .y = self.y * v};
}
}
}
// OR:
class Point4b {
var x: f64;
var y: f64;
external impl as Vector {
fn Add[self: Self](b: Self) -> Self {
return {.x = self.x + b.x, .y = self.y + b.y};
}
fn Scale[self: Self](v: f64) -> Self {
return {.x = self.x * v, .y = self.y * v};
}
}
alias Add = Vector.Add;
}
// OR:
class Point4c {
var x: f64;
var y: f64;
fn Add[self: Self](b: Self) -> Self {
return {.x = self.x + b.x, .y = self.y + b.y};
}
}
external impl Point4c as Vector {
alias Add = Point4c.Add; // Syntax TBD
fn Scale[self: Self](v: f64) -> Self {
return {.x = self.x * v, .y = self.y * v};
}
}
```
Being defined lexically inside the class means that implementation is available
to other members defined in the class. For example, it would allow implementing
another interface or method that requires this interface to be implemented.
**Open question:** Do implementations need to be defined lexically inside the
class to get access to private members, or is it sufficient to be defined in the
same library as the class?
**Rejected alternative:** We could allow types to have different APIs in
different files based on explicit configuration in that file. For example, we
could support a declaration that a given interface or a given method of an
interface is "in scope" for a particular type in this file. With that
declaration, the method could be called using
[simple member access](terminology.md#simple-member-access). This avoids most
concerns arising from name collisions between interfaces. It has a few downsides
though:
- It increases variability between files, since the same type will have
different APIs depending on these declarations. This makes it harder to
copy-paste code between files.
- It makes reading code harder, since you have to search the file for these
declarations that affect name lookup.
**Comparison with other languages:** Both Rust and Swift support external
implementation.
[Swift's syntax](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277)
does this as an "extension" of the original type. In Rust, all implementations
are external as in
[this example](https://doc.rust-lang.org/rust-by-example/trait.html). Unlike
Swift and Rust, we don't allow a type's API to be modified outside its
definition. So in Carbon a type's API is consistent no matter what is imported,
unlike Swift and Rust.
### Qualified member names and compound member access
Given a value of type `Point3` and an interface `Vector` implemented for that
type, you can access the methods from that interface using a
[qualified member access expression](terminology.md#qualified-member-access-expression)
whether or not the implementation is done externally with an `external impl`
declaration. The qualified member access expression writes the member's
_qualified name_ in the parentheses of the
[compound member access syntax](/docs/design/expressions/member_access.md):
```
var p1: Point3 = {.x = 1.0, .y = 2.0};
var p2: Point3 = {.x = 2.0, .y = 4.0};
Assert(p1.(Vector.Scale)(2.0) == p2);
Assert(p1.(Vector.Add)(p1) == p2);
```
Note that the name in the parens is looked up in the containing scope, not in
the names of members of `Point3`. So if there was another interface `Drawable`
with method `Draw` defined in the `Plot` package also implemented for `Point3`,
as in:
```
package Plot;
import Points;
interface Drawable {
fn Draw[self: Self]();
}
external impl Points.Point3 as Drawable { ... }
```
You could access `Draw` with a qualified name:
```
import Plot;
import Points;
var p: Points.Point3 = {.x = 1.0, .y = 2.0};
p.(Plot.Drawable.Draw)();
```
**Comparison with other languages:** This is intended to be analogous to, in
C++, adding `ClassName::` in front of a member name to disambiguate, such as
[names defined in both a parent and child class](https://stackoverflow.com/questions/357307/how-to-call-a-parent-class-function-from-derived-class-function).
### Access
An `impl` must be visible to all code that can see both the type and the
interface being implemented:
- If either the type or interface is private to a single file, then since the
only way to define the `impl` is to use that private name, the `impl` must
be defined private to that file as well.
- Otherwise, if the type or interface is private but declared in an API file,
then the `impl` must be declared in the same file so the existence of that
`impl` is visible to all files in that library.
- Otherwise, the `impl` must be defined in the public API file of the library,
so it is visible in all places that might use it.
No access control modifiers are allowed on `impl` declarations, an `impl` is
always visible to the intersection of the visibility of all names used in the
declaration of the `impl`.
## Generics
Here is a function that can accept values of any type that has implemented the
`Vector` interface:
```
fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: f64) -> T {
return a.Add(b).Scale(s);
}
var v: Point = AddAndScaleGeneric(a, w, 2.5);
```
Here `T` is a type whose type is `Vector`. The `:!` syntax means that `T` is a
_[generic parameter](terminology.md#generic-versus-template-parameters)_. That
means it must be known to the caller, but we will only use the information
present in the signature of the function to type check the body of
`AddAndScaleGeneric`'s definition. In this case, we know that any value of type
`T` implements the `Vector` interface and so has an `Add` and a `Scale` method.
**References:** The `:!` syntax was accepted in
[proposal #676](https://github.com/carbon-language/carbon-lang/pull/676).
Names are looked up in the body of `AddAndScaleGeneric` for values of type `T`
in `Vector`. This means that `AddAndScaleGeneric` is interpreted as equivalent
to adding a `Vector`
[qualification](#qualified-member-names-and-compound-member-access) to replace
all simple member accesses of `T`:
```
fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: Double) -> T {
return a.(Vector.Add)(b).(Vector.Scale)(s);
}
```
With these qualifications, the function can be type-checked for any `T`
implementing `Vector`. This type checking is equivalent to type checking the
function with `T` set to an [archetype](terminology.md#archetype) of `Vector`.
An archetype is a placeholder type considered to satisfy its constraint, which
is `Vector` in this case, and no more. It acts as the most general type
satisfying the interface. The effect of this is that an archetype of `Vector`
acts like a [supertype](https://en.wikipedia.org/wiki/Subtyping) of any `T`
implementing `Vector`.
For name lookup purposes, an archetype is considered to have
[implemented its constraint internally](terminology.md#internal-impl). The only
oddity is that the archetype may have different names for members than specific
types `T` that implement interfaces from the constraint
[externally](terminology.md#external-impl). This difference in names can also
occur for supertypes in C++, for example members in a derived class can hide
members in the base class with the same name, though it is not that common for
it to come up in practice.
The behavior of calling `AddAndScaleGeneric` with a value of a specific type
like `Point` is to set `T` to `Point` after all the names have been qualified.
```
// AddAndScaleGeneric with T = Point
fn AddAndScaleForPoint(a: Point, b: Point, s: Double) -> Point {
return a.(Vector.Add)(b).(Vector.Scale)(s);
}
```
This qualification gives a consistent interpretation to the body of the function
even when the type supplied by the caller
[implements the interface externally](terminology.md#external-impl), as `Point2`
does:
```
// AddAndScaleGeneric with T = Point2
fn AddAndScaleForPoint2(a: Point2, b: Point2, s: Double) -> Point2 {
// ✅ This works even though `a.Add(b).Scale(s)` wouldn't.
return a.(Vector.Add)(b).(Vector.Scale)(s);
}
```
### Return type
From the caller's perspective, the return type is the result of substituting the
caller's values for the generic parameters into the return type expression. So
`AddAndScaleGeneric` called with `Point` values returns a `Point` and called
with `Point2` values returns a `Point2`. So looking up a member on the resulting
value will look in `Point` or `Point2` rather than `Vector`.
This is part of realizing
[the goal that generic functions can be used in place of regular functions without changing the return type that callers see](goals.md#path-from-regular-functions).
In this example, `AddAndScaleGeneric` can be substituted for
`AddAndScaleForPoint` and `AddAndScaleForPoint2` without affecting the return
types. This requires the return value to be converted to the type that the
caller expects instead of the erased type used inside the generic function.
A generic caller of a generic function performs the same substitution process to
determine the return type, but the result may be generic. In this example of
calling a generic from another generic,
```
fn DoubleThreeTimes[U:! Vector](a: U) -> U {
return AddAndScaleGeneric(a, a, 2.0).Scale(2.0);
}
```
the return type of `AddAndScaleGeneric` is found by substituting in the `U` from
`DoubleThreeTimes` for the `T` from `AddAndScaleGeneric` in the return type
expression of `AddAndScaleGeneric`. `U` is an archetype of `Vector`, and so
implements `Vector` internally and therefore has a `Scale` method.
If `U` had a more specific type, the return value would have the additional
capabilities of `U`. For example, given a parameterized type `GeneralPoint`
implementing `Vector`, and a function that takes a `GeneralPoint` and calls
`AddAndScaleGeneric` with it:
```
class GeneralPoint(C:! Numeric) {
external impl as Vector { ... }
fn Get[self: Self](i: i32) -> C;
}
fn CallWithGeneralPoint[C:! Numeric](p: GeneralPoint(C)) -> C {
// `AddAndScaleGeneric` returns `T` and in these calls `T` is
// deduced to be `GeneralPoint(C)`.
// ❌ Illegal: AddAndScaleGeneric(p, p, 2.0).Scale(2.0);
// `GeneralPoint(C)` implements `Vector` externally, and so
// does not have a `Scale` method.
// ✅ Allowed: `GeneralPoint(C)` has a `Get` method
AddAndScaleGeneric(p, p, 2.0).Get(0);
// ✅ Allowed: `GeneralPoint(C)` implements `Vector`
// externally, and so has a `Vector.Scale` method.
// `Vector.Scale` returns `Self` which is `GeneralPoint(C)`
// again, and so has a `Get` method.
return AddAndScaleGeneric(p, p, 2.0).(Vector.Scale)(2.0).Get(0);
}
```
The result of the call to `AddAndScaleGeneric` from `CallWithGeneralPoint` has
type `GeneralPoint(C)` and so has a `Get` method and a `Vector.Scale` method.
But, in contrast to how `DoubleThreeTimes` works, since `Vector` is implemented
externally the return value in this case does not directly have a `Scale`
method.
### Implementation model
A possible model for generating code for a generic function is to use a
[witness table](terminology.md#witness-tables) to represent how a type
implements an interface:
- [Interfaces](#interfaces) are types of witness tables.
- An [impl](#implementing-interfaces) is a witness table value.
Type checking is done with just the interface. The impl is used during code
generation time, possibly using
[monomorphization](https://en.wikipedia.org/wiki/Monomorphization) to have a
separate instantiation of the function for each combination of the generic
argument values. The compiler is free to use other implementation strategies,
such as passing the witness table for any needed implementations, if that can be
predicted.
For the example above, [the Vector interface](#interfaces) could be thought of
defining a witness table type like:
```
class Vector {
// `Self` is the representation type, which is only
// known at compile time.
var Self:! type;
// `fnty` is **placeholder** syntax for a "function type",
// so `Add` is a function that takes two `Self` parameters
// and returns a value of type `Self`.
var Add: fnty(a: Self, b: Self) -> Self;
var Scale: fnty(a: Self, v: f64) -> Self;
}
```
The [impl of Vector for Point](#implementing-interfaces) would be a value of
this type:
```
var VectorForPoint: Vector = {
.Self = Point,
// `lambda` is **placeholder** syntax for defining a
// function value.
.Add = lambda(a: Point, b: Point) -> Point {
return {.x = a.x + b.x, .y = a.y + b.y};
},
.Scale = lambda(a: Point, v: f64) -> Point {
return {.x = a.x * v, .y = a.y * v};
},
};
```
Since generic arguments (where the parameter is declared using `:!`) are passed
at compile time, so the actual value of `VectorForPoint` can be used to generate
the code for functions using that impl. This is the
[static-dispatch witness table](terminology.md#static-dispatch-witness-table)
approach.
## Interfaces recap
Interfaces have a name and a definition.
The definition of an interface consists of a set of declarations. Each
declaration defines a requirement for any `impl` that is in turn a capability
that consumers of that `impl` can rely on. Typically those declarations also
have names, useful for both saying how the `impl` satisfies the requirement and
accessing the capability.
Interfaces are ["nominal"](terminology.md#nominal-interfaces), which means their
name is significant. So two interfaces with the same body definition but
different names are different, just like two classes with the same definition
but different names are considered different types. For example, lets say we
define another interface, say `LegoFish`, with the same `Add` and `Scale` method
signatures. Implementing `Vector` would not imply an implementation of
`LegoFish`, because the `impl` definition explicitly refers to the name
`Vector`.
An interface's name may be used in a few different contexts:
- to define [an `impl` for a type](#implementing-interfaces),
- as a namespace name in
[a qualified name](#qualified-member-names-and-compound-member-access), and
- as a [type-of-type](terminology.md#type-of-type) for
[a generic type parameter](#generics).
While interfaces are examples of type-of-types, type-of-types are a more general
concept, for which interfaces are a building block.
## Type-of-types
A [type-of-type](terminology.md#type-of-type) consists of a set of requirements
and a set of names. Requirements are typically a set of interfaces that a type
must satisfy, though other kinds of requirements are added below. The names are
aliases for qualified names in those interfaces.
An interface is one particularly simple example of a type-of-type. For example,
`Vector` as a type-of-type has a set of requirements consisting of the single
interface `Vector`. Its set of names consists of `Add` and `Scale` which are
aliases for the corresponding qualified names inside `Vector` as a namespace.
The requirements determine which types are values of a given type-of-type. The
set of names in a type-of-type determines the API of a generic type value and
define the result of [member access](/docs/design/expressions/member_access.md)
into the type-of-type.
This general structure of type-of-types holds not just for interfaces, but
others described in the rest of this document.
## Named constraints
If the interfaces discussed above are the building blocks for type-of-types,
[generic named constraints](terminology.md#named-constraints) describe how they
may be composed together. Unlike interfaces which are nominal, the name of a
named constraint is not a part of its value. Two different named constraints
with the same definition are equivalent even if they have different names. This
is because types don't have to explicitly specify which named constraints they
implement, types automatically implement any named constraints they can satisfy.
A named constraint definition can contain interface requirements using `impl`
declarations and names using `alias` declarations. Note that this allows us to
declare the aspects of a type-of-type directly.
```
constraint VectorLegoFish {
// Interface implementation requirements
impl as Vector;
impl as LegoFish;
// Names
alias Scale = Vector.Scale;
alias VAdd = Vector.Add;
alias LFAdd = LegoFish.Add;
}
```
An `impl` requirement may alternatively be on a named constraint, instead of an
interface, to add all the requirements of another named constraint without
adding any of the names:
```
constraint DrawVectorLegoFish {
// The same as requiring both `Vector` and `LegoFish`.
impl as VectorLegoFish;
// A regular interface requirement. No syntactic difference.
impl as Drawable;
}
```
In general, Carbon makes no syntactic distinction between the uses of named
constraints and interfaces, so one may be replaced with the other without
affecting users. To accomplish this, Carbon allows a named constraint to be used
whenever an interface may be. This includes all of these
[uses of interfaces](#interfaces-recap):
- A type may `impl` a named constraint to say that it implements all of the
requirements of the named constraint, as
[described below](#extends-and-impl-with-named-constraints).
- A named constraint may be used as a namespace name in
[a qualified name](#qualified-member-names-and-compound-member-access). For
example, `VectorLegoFish.VAdd` refers to the same name as `Vector.Add`.
- A named constraint may be used as a
[type-of-type](terminology.md#type-of-type) for
[a generic type parameter](#generics).
We don't expect developers to directly define many named constraints, but other
constructs we do expect them to use will be defined in terms of them. For
example, if `type` were not a keyword, we could define the Carbon builtin `type`
as:
```
constraint type { }
```
That is, `type` is the type-of-type with no requirements (so matches every
type), and defines no names.
```
fn Identity[T:! type](x: T) -> T {
// Can accept values of any type. But, since we know nothing about the
// type, we don't know about any operations on `x` inside this function.
return x;
}
var i: i32 = Identity(3);
var s: String = Identity("string");
```
**Aside:** We can define `auto` as syntactic sugar for `(template _:! type)`.
This definition allows you to use `auto` as the type for a local variable whose
type can be statically determined by the compiler. It also allows you to use
`auto` as the type of a function parameter, to mean "accepts a value of any
type, and this function will be instantiated separately for every different
type." This is consistent with the
[use of `auto` in the C++20 Abbreviated function template feature](https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template).
In general, the declarations in `constraint` definition match a subset of the
declarations in an `interface`. Named constraints used with generics, as opposed
to templates, should only include required interfaces and aliases to named
members of those interfaces.
To declare a named constraint that includes other declarations for use with
template parameters, use the `template` keyword before `constraint`. Method,
associated type, and associated function requirements may only be declared
inside a `template constraint`. Note that a generic constraint ignores the names
of members defined for a type, but a template constraint can depend on them.
There is an analogy between declarations used in a `constraint` and in an
`interface` definition. If an `interface` `I` has (non-`alias`) declarations
`X`, `Y`, and `Z`, like so:
```
interface I {
X;
Y;
Z;
}
```
Then a type implementing `I` would have `impl as I` with definitions for `X`,
`Y`, and `Z`, as in:
```
class ImplementsI {
// ...
impl as I {
X { ... }
Y { ... }
Z { ... }
}
}
```
But the corresponding `constraint` or `template constraint`, `S`:
```
// or template constraint S {
constraint S {
X;
Y;
Z;
}
```
would match any type with definitions for `X`, `Y`, and `Z` directly:
```
class ImplementsS {
// ...
X { ... }
Y { ... }
Z { ... }
}
```
**TODO:** Move the `template constraint` and `auto` content to the template
design document, once it exists.
### Subtyping between type-of-types
There is a subtyping relationship between type-of-types that allows calls of one
generic function from another as long as it has a subset of the requirements.
Given a generic type variable `T` with type-of-type `I1`, it satisfies a
type-of-type `I2` as long as the requirements of `I1` are a superset of the
requirements of `I2`. This means a value `x` of type `T` may be passed to
functions requiring types to satisfy `I2`, as in this example:
```
interface Printable { fn Print[self: Self](); }
interface Renderable { fn Draw[self: Self](); }
constraint PrintAndRender {
impl as Printable;
impl as Renderable;
}
constraint JustPrint {
impl as Printable;
}
fn PrintIt[T2:! JustPrint](x2: T2) {
x2.(Printable.Print)();
}
fn PrintDrawPrint[T1:! PrintAndRender](x1: T1) {
// x1 implements `Printable` and `Renderable`.
x1.(Printable.Print)();
x1.(Renderable.Draw)();
// Can call `PrintIt` since `T1` satisfies `JustPrint` since
// it implements `Printable` (in addition to `Renderable`).
PrintIt(x1);
}
```
## Combining interfaces by anding type-of-types
In order to support functions that require more than one interface to be
implemented, we provide a combination operator on type-of-types, written `&`.
This operator gives the type-of-type with the union of all the requirements and
the union of the names minus any conflicts.
```
interface Printable {
fn Print[self: Self]();
}
interface Renderable {
fn Center[self: Self]() -> (i32, i32);
fn Draw[self: Self]();
}
// `Printable & Renderable` is syntactic sugar for this type-of-type:
constraint {
impl as Printable;
impl as Renderable;
alias Print = Printable.Print;
alias Center = Renderable.Center;
alias Draw = Renderable.Draw;
}
fn PrintThenDraw[T:! Printable & Renderable](x: T) {
// Can use methods of `Printable` or `Renderable` on `x` here.
x.Print(); // Same as `x.(Printable.Print)();`.
x.Draw(); // Same as `x.(Renderable.Draw)();`.
}
class Sprite {
// ...
impl as Printable {
fn Print[self: Self]() { ... }
}
impl as Renderable {
fn Center[self: Self]() -> (i32, i32) { ... }
fn Draw[self: Self]() { ... }
}
}
var s: Sprite = ...;
PrintThenDraw(s);
```
Any conflicting names between the two types are replaced with a name that is an
error to use.
```
interface Renderable {
fn Center[self: Self]() -> (i32, i32);
fn Draw[self: Self]();
}
interface EndOfGame {
fn Draw[self: Self]();
fn Winner[self: Self](player: i32);
}
// `Renderable & EndOfGame` is syntactic sugar for this type-of-type:
constraint {
impl as Renderable;
impl as EndOfGame;
alias Center = Renderable.Center;
// Open question: `forbidden`, `invalid`, or something else?
forbidden Draw
message "Ambiguous, use either `(Renderable.Draw)` or `(EndOfGame.Draw)`.";
alias Winner = EndOfGame.Winner;
}
```
Conflicts can be resolved at the call site using a
[qualified member access expression](#qualified-member-names-and-compound-member-access),
or by defining a named constraint explicitly and renaming the methods:
```
constraint RenderableAndEndOfGame {
impl as Renderable;
impl as EndOfGame;
alias Center = Renderable.Center;
alias RenderableDraw = Renderable.Draw;
alias TieGame = EndOfGame.Draw;
alias Winner = EndOfGame.Winner;
}
fn RenderTieGame[T:! RenderableAndEndOfGame](x: T) {
// Calls Renderable.Draw()
x.RenderableDraw();
// Calls EndOfGame.Draw()
x.TieGame();
}
```
Reserving the name when there is a conflict is part of resolving what happens
when you combine more than two type-of-types. If `x` is forbidden in `A`, it is
forbidden in `A & B`, whether or not `B` defines the name `x`. This makes `&`
associative and commutative, and so it is well defined on sets of interfaces, or
other type-of-types, independent of order.
Note that we do _not_ consider two type-of-types using the same name to mean the
same thing to be a conflict. For example, combining a type-of-type with itself
gives itself, `MyTypeOfType & MyTypeOfType == MyTypeOfType`. Also, given two
[interface extensions](#interface-extension) of a common base interface, the
combination should not conflict on any names in the common base.
**Rejected alternative:** Instead of using `&` as the combining operator, we
considered using `+`,
[like Rust](https://rust-lang.github.io/rfcs/0087-trait-bounds-with-plus.html).
See [#531](https://github.com/carbon-language/carbon-lang/issues/531) for the
discussion.
**Future work:** We may want to define another operator on type-of-types for
adding requirements to a type-of-type without affecting the names, and so avoid
the possibility of name conflicts. Note this means the operation is not
commutative. If we call this operator `[&]`, then `A [&] B` has the names of `A`
and `B [&] A` has the names of `B`.
```
// `Printable [&] Renderable` is syntactic sugar for this type-of-type:
constraint {
impl as Printable;
impl as Renderable;
alias Print = Printable.Print;
}
// `Renderable [&] EndOfGame` is syntactic sugar for this type-of-type:
constraint {
impl as Renderable;
impl as EndOfGame;
alias Center = Renderable.Center;
alias Draw = Renderable.Draw;
}
```
Note that all three expressions `A & B`, `A [&] B`, and `B [&] A` have the same
requirements, and so you would be able to switch a function declaration between
them without affecting callers.
Nothing in this design depends on the `[&]` operator, and having both `&` and
`[&]` might be confusing for users, so it makes sense to postpone implementing
`[&]` until we have a demonstrated need. The `[&]` operator seems most useful
for adding requirements for interfaces used for
[operator overloading](#operator-overloading), where merely implementing the
interface is enough to be able to use the operator to access the functionality.
**Alternatives considered:** See
[Carbon: Access to interface methods](https://docs.google.com/document/d/17IXDdu384x1t9RimQ01bhx4-nWzs4ZEeke4eO6ImQNc/edit?resourcekey=0-Fe44R-0DhQBlw0gs2ujNJA).
**Comparison with other languages:** This `&` operation on interfaces works very
similarly to Rust's `+` operation, with the main difference being how you
[qualify names when there is a conflict](https://doc.rust-lang.org/rust-by-example/trait/disambiguating.html).
## Interface requiring other interfaces
Some interfaces will depend on other interfaces being implemented for the same
type. For example, in C++,
[the `Container` concept](https://en.cppreference.com/w/cpp/named_req/Container#Other_requirements)
requires all containers to also satisfy the requirements of
`DefaultConstructible`, `CopyConstructible`, `EqualityComparable`, and
`Swappable`. This is already a capability for
[type-of-types in general](#type-of-types). For consistency we will use the same
semantics and syntax as we do for [named constraints](#named-constraints):
```
interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; }
interface Iterable {
fn Advance[addr self: Self*]() -> bool;
impl as Equatable;
}
def DoAdvanceAndEquals[T:! Iterable](x: T) {
// `x` has type `T` that implements `Iterable`, and so has `Advance`.
x.Advance();
// `Iterable` requires an implementation of `Equatable`,
// so `T` also implements `Equatable`.
x.(Equatable.Equals)(x);
}
class Iota {
impl as Iterable { fn Advance[self: Self]() { ... } }
impl as Equatable { fn Equals[self: Self](rhs: Self) -> bool { ... } }
}
var x: Iota;
DoAdvanceAndEquals(x);
```
Like with named constraints, an interface implementation requirement doesn't by
itself add any names to the interface, but again those can be added with `alias`
declarations:
```
interface Hashable {
fn Hash[self: Self]() -> u64;
impl as Equatable;
alias Equals = Equatable.Equals;
}
def DoHashAndEquals[T:! Hashable](x: T) {
// Now both `Hash` and `Equals` are available directly:
x.Hash();
x.Equals(x);
}
```
**Comparison with other languages:**
[This feature is called "Supertraits" in Rust](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-supertraits-to-require-one-traits-functionality-within-another-trait).
**Note:** The design for this feature is continued in
[a later section](#interface-requiring-other-interfaces-revisited).
### Interface extension
When implementing an interface, we should allow implementing the aliased names
as well. In the case of `Hashable` above, this includes all the members of
`Equatable`, obviating the need to implement `Equatable` itself:
```
class Song {
impl as Hashable {
fn Hash[self: Self]() -> u64 { ... }
fn Equals[self: Self](rhs: Self) -> bool { ... }
}
}
var y: Song;
DoHashAndEquals(y);
```
This allows us to say that `Hashable`
["extends"](terminology.md#extending-an-interface) `Equatable`, with some
benefits:
- This allows `Equatable` to be an implementation detail of `Hashable`.
- This allows types implementing `Hashable` to implement all of its API in one
place.
- This reduces the boilerplate for types implementing `Hashable`.
We expect this concept to be common enough to warrant dedicated syntax:
```
interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; }
interface Hashable {
extends Equatable;
fn Hash[self: Self]() -> u64;
}
// is equivalent to the definition of Hashable from before:
// interface Hashable {
// impl as Equatable;
// alias Equals = Equatable.Equals;
// fn Hash[self: Self]() -> u64;
// }
```
No names in `Hashable` are allowed to conflict with names in `Equatable` (unless
those names are marked as `upcoming` or `deprecated` as in
[evolution future work](#evolution)). Hopefully this won't be a problem in
practice, since interface extension is a very closely coupled relationship, but
this may be something we will have to revisit in the future.
Examples:
- The C++
[Boost.Graph library](https://www.boost.org/doc/libs/1_74_0/libs/graph/doc/)
[graph concepts](https://www.boost.org/doc/libs/1_74_0/libs/graph/doc/graph_concepts.html#fig:graph-concepts)
has many refining relationships between concepts.
[Carbon generics use case: graph library](https://docs.google.com/document/d/15Brjv8NO_96jseSesqer5HbghqSTJICJ_fTaZOH0Mg4/edit?usp=sharing&resourcekey=0-CYSbd6-xF8vYHv9m1rolEQ)
shows how those concepts might be translated into Carbon interfaces.
- The [C++ concepts](https://en.cppreference.com/w/cpp/named_req) for
containers, iterators, and concurrency include many requirement
relationships.
- Swift protocols, such as
[Collection](https://developer.apple.com/documentation/swift/collection).
To write an interface extending multiple interfaces, use multiple `extends`
declarations. For example, the
[`BinaryInteger` protocol in Swift](https://developer.apple.com/documentation/swift/binaryinteger)
inherits from `CustomStringConvertible`, `Hashable`, `Numeric`, and `Stridable`.
The [`SetAlgebra` protocol](https://swiftdoc.org/v5.1/protocol/setalgebra/)
extends `Equatable` and `ExpressibleByArrayLiteral`, which would be declared in
Carbon:
```
interface SetAlgebra {
extends Equatable;
extends ExpressibleByArrayLiteral;
}
```
**Alternative considered:** The `extends` declarations are in the body of the
`interface` definition instead of the header so we can use
[associated types (defined below)](#associated-types) also defined in the body
in parameters or constraints of the interface being extended.
```
// A type can implement `ConvertibleTo` many times, using
// different values of `T`.
interface ConvertibleTo(T:! type) { ... }
// A type can only implement `PreferredConversion` once.
interface PreferredConversion {
let AssociatedType:! type;
extends ConvertibleTo(AssociatedType);
}
```
#### `extends` and `impl` with named constraints
The `extends` declaration makes sense with the same meaning inside a
[`constraint`](#named-constraints) definition, and so is also supported.
```
interface Media {
fn Play[self: Self]();
}
interface Job {
fn Run[self: Self]();
}
constraint Combined {
extends Media;
extends Job;
}
```
This definition of `Combined` is equivalent to requiring both the `Media` and
`Job` interfaces being implemented, and aliases their methods.
```
// Equivalent
constraint Combined {
impl as Media;
alias Play = Media.Play;
impl as Job;
alias Run = Job.Run;
}
```
Notice how `Combined` has aliases for all the methods in the interfaces it
requires. That condition is sufficient to allow a type to `impl` the named
constraint:
```
class Song {
impl as Combined {
fn Play[self: Self]() { ... }
fn Run[self: Self]() { ... }
}
}
```
This is equivalent to implementing the required interfaces directly:
```
class Song {
impl as Media {
fn Play[self: Self]() { ... }
}
impl as Job {
fn Run[self: Self]() { ... }
}
}
```
This is just like when you get an implementation of `Equatable` by implementing
`Hashable` when `Hashable` extends `Equatable`. This provides a tool useful for
[evolution](#evolution).
Conversely, an `interface` can extend a `constraint`:
```
interface MovieCodec {
extends Combined;
fn Load[addr self: Self*](filename: String);
}
```
This gives `MovieCodec` the same requirements and names as `Combined`, and so is
equivalent to:
```
interface MovieCodec {
impl as Media;
alias Play = Media.Play;
impl as Job;
alias Run = Job.Run;
fn Load[addr self: Self*](filename: String);
}
```
#### Diamond dependency issue
Consider this set of interfaces, simplified from
[this example generic graph library doc](https://docs.google.com/document/d/15Brjv8NO_96jseSesqer5HbghqSTJICJ_fTaZOH0Mg4/edit?usp=sharing&resourcekey=0-CYSbd6-xF8vYHv9m1rolEQ):
```
interface Graph {
fn Source[addr self: Self*](e: EdgeDescriptor) -> VertexDescriptor;
fn Target[addr self: Self*](e: EdgeDescriptor) -> VertexDescriptor;
}
interface IncidenceGraph {
extends Graph;
fn OutEdges[addr self: Self*](u: VertexDescriptor)
-> (EdgeIterator, EdgeIterator);
}
interface EdgeListGraph {
extends Graph;
fn Edges[addr self: Self*]() -> (EdgeIterator, EdgeIterator);
}
```
We need to specify what happens when a graph type implements both
`IncidenceGraph` and `EdgeListGraph`, since both interfaces extend the `Graph`
interface.
```
class MyEdgeListIncidenceGraph {
impl as IncidenceGraph { ... }
impl as EdgeListGraph { ... }
}
```
The rule is that we need one definition of each method of `Graph`. Each method
though could be defined in the `impl` block of `IncidenceGraph`,
`EdgeListGraph`, or `Graph`. These would all be valid:
- `IncidenceGraph` implements all methods of `Graph`, `EdgeListGraph`
implements none of them.
```
class MyEdgeListIncidenceGraph {
impl as IncidenceGraph {
fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
fn Target[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
fn OutEdges[addr self: Self*](u: VertexDescriptor)
-> (EdgeIterator, EdgeIterator) { ... }
}
impl as EdgeListGraph {
fn Edges[addr self: Self*]() -> (EdgeIterator, EdgeIterator) { ... }
}
}
```
- `IncidenceGraph` and `EdgeListGraph` implement all methods of `Graph`
between them, but with no overlap.
```
class MyEdgeListIncidenceGraph {
impl as IncidenceGraph {
fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
fn OutEdges[addr self: Self*](u: VertexDescriptor)
-> (EdgeIterator, EdgeIterator) { ... }
}
impl as EdgeListGraph {
fn Target[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
fn Edges[addr self: Self*]() -> (EdgeIterator, EdgeIterator) { ... }
}
}
```
- Explicitly implementing `Graph`.
```
class MyEdgeListIncidenceGraph {
impl as Graph {
fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
fn Target[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
}
impl as IncidenceGraph { ... }
impl as EdgeListGraph { ... }
}
```
- Implementing `Graph` externally.
```
class MyEdgeListIncidenceGraph {
impl as IncidenceGraph { ... }
impl as EdgeListGraph { ... }
}
external impl MyEdgeListIncidenceGraph as Graph {
fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
fn Target[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... }
}
```
This last point means that there are situations where we can only detect a
missing method definition by the end of the file. This doesn't delay other
aspects of semantic checking, which will just assume that these methods will
eventually be provided.
**Open question:** We could require that the `external impl` of the required
interface be declared lexically in the class scope in this case. That would
allow earlier detection of missing definitions.
### Use case: overload resolution
Implementing an extended interface is an example of a more specific match for
[lookup resolution](#lookup-resolution-and-specialization). For example, this
could be used to provide different implementations of an algorithm depending on
the capabilities of the iterator being passed in:
```
interface ForwardIntIterator {
fn Advance[addr self: Self*]();
fn Get[self: Self]() -> i32;
}
interface BidirectionalIntIterator {
extends ForwardIntIterator;
fn Back[addr self: Self*]();
}
interface RandomAccessIntIterator {
extends BidirectionalIntIterator;
fn Skip[addr self: Self*](offset: i32);
fn Difference[self: Self](rhs: Self) -> i32;
}
fn SearchInSortedList[IterT:! ForwardIntIterator]
(begin: IterT, end: IterT, needle: i32) -> bool {
... // does linear search
}
// Will prefer the following overload when it matches
// since it is more specific.
fn SearchInSortedList[IterT:! RandomAccessIntIterator]
(begin: IterT, end: IterT, needle: i32) -> bool {
... // does binary search
}
```
This would be an example of the more general rule that an interface `A`
requiring an implementation of interface `B` means `A` is more specific than
`B`.
## Adapting types
Since interfaces may only be implemented for a type once, and we limit where
implementations may be added to a type, there is a need to allow the user to
switch the type of a value to access different interface implementations. Carbon
therefore provides a way to create new types
[compatible with](terminology.md#compatible-types) existing types with different
APIs, in particular with different interface implementations, by
[adapting](terminology.md#adapting-a-type) them:
```
interface Printable {
fn Print[self: Self]();
}
interface Comparable {
fn Less[self: Self](rhs: Self) -> bool;
}
class Song {
impl as Printable { fn Print[self: Self]() { ... } }
}
adapter SongByTitle for Song {
impl as Comparable {
fn Less[self: Self](rhs: Self) -> bool { ... }
}
}
adapter FormattedSong for Song {
impl as Printable { fn Print[self: Self]() { ... } }
}
adapter FormattedSongByTitle for Song {
impl as Printable = FormattedSong;
impl as Comparable = SongByTitle;
}
```
This allows developers to provide implementations of new interfaces (as in
`SongByTitle`), provide different implementations of the same interface (as in
`FormattedSong`), or mix and match implementations from other compatible types
(as in `FormattedSongByTitle`). The rules are:
- You can add any declaration that you could add to a class except for
declarations that would change the representation of the type. This means
you can add methods, functions, interface implementations, and aliases, but
not fields, base classes, or virtual functions. The specific implementations
of virtual functions are part of the type representation, and so no virtual
functions may be overridden in an adapter either.
- The adapted type is compatible with the original type, and that relationship
is an equivalence class, so all of `Song`, `SongByTitle`, `FormattedSong`,
and `FormattedSongByTitle` end up compatible with each other.
- Since adapted types are compatible with the original type, you may
explicitly cast between them, but there is no implicit conversion between
these types.
Inside an adapter, the `Self` type matches the adapter. Members of the original
type may be accessed either by a cast:
```
adapter SongByTitle for Song {
impl as Comparable {
fn Less[self: Self](rhs: Self) -> bool {
return (self as Song).Title() < (rhs as Song).Title();
}
}
}
```
or using a qualified member access expression:
```
adapter SongByTitle for Song {
impl as Comparable {
fn Less[self: Self](rhs: Self) -> bool {
return self.(Song.Title)() < rhs.(Song.Title)();
}
}
}
```
**Comparison with other languages:** This matches the Rust idiom called
"newtype", which is used to implement traits on types while avoiding coherence
problems, see
[here](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types)
and
[here](https://github.com/Ixrec/rust-orphan-rules#user-content-why-are-the-orphan-rules-controversial).
Rust's mechanism doesn't directly support reusing implementations, though some
of that is provided by macros defined in libraries. Haskell has a
[`newtype` feature](https://wiki.haskell.org/Newtype) as well. Haskell's feature
doesn't directly support reusing implementations either, but the most popular
compiler provides it as
[an extension](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/newtype_deriving.html).
### Adapter compatibility
Consider a type with a generic type parameter, like a hash map:
```
interface Hashable { ... }
class HashMap(KeyT:! Hashable, ValueT:! type) {
fn Find[self: Self](key: KeyT) -> Optional(ValueT);
// ...
}
```
A user of this type will provide specific values for the key and value types:
```
class Song {
impl as Hashable { ... }
// ...
}
var play_count: HashMap(Song, i32) = ...;
var thriller_count: Optional(i32) =
play_count.Find(Song("Thriller"));
```
Since the `Find` function is generic, it can only use the capabilities that
`HashMap` requires of `KeyT` and `ValueT`. This allows us to evaluate when we
can convert between two different arguments to a parameterized type. Consider
two adapters of `Song` that implement `Hashable`:
```
adapter PlayableSong for Song {
impl as Hashable = Song;
impl as Media { ... }
}
adapter SongHashedByTitle for Song {
impl as Hashable { ... }
}
```
`Song` and `PlayableSong` have the same implementation of `Hashable` in addition
to using the same data representation. This means that it is safe to convert
between `HashMap(Song, i32)` and `HashMap(PlayableSong, i32)`, because the
implementation of all the methods will use the same implementation of the
`Hashable` interface. Carbon permits this conversion with an explicit cast.
On the other hand, `SongHashedByTitle` has a different implementation of
`Hashable` than `Song`. So even though `Song` and `SongHashedByTitle` are
compatible types, `HashMap(Song, i32)` and `HashMap(SongHashedByTitle, i32)` are
incompatible. This is important because we know that in practice the invariants
of a `HashMap` implementation rely on the hashing function staying the same.
### Extending adapter
Frequently we expect that the adapter type will want to preserve most or all of
the API of the original type. The two most common cases expected are adding and
replacing an interface implementation. Users would indicate that an adapter
starts from the original type's existing API by using the `extends` keyword
instead of `for`:
```
class Song {
impl as Hashable { ... }
impl as Printable { ... }
}
adapter SongByArtist extends Song {
// Add an implementation of a new interface
impl as Comparable { ... }
// Replace an existing implementation of an interface
// with an alternative.
impl as Hashable { ... }
}
```
The resulting type `SongByArtist` would:
- implement `Comparable`, unlike `Song`,
- implement `Hashable`, but differently than `Song`, and
- implement `Printable`, inherited from `Song`.
Unlike the similar `class B extends A` notation, `adapter B extends A` is
permitted even if `A` is a final class. Also, there is no implicit conversion
from `B` to `A`, matching `adapter`...`for` but unlike class extension.
To avoid or resolve name conflicts between interfaces, an `impl` may be declared
[`external`](#external-impl). The names in that interface may then be pulled in
individually or renamed using `alias` declarations.
```
adapter SongRenderToPrintDriver extends Song {
// Add a new `Print()` member function.
fn Print[self: Self]() { ... }
// Avoid name conflict with new `Print` function by making
// the implementation of the `Printable` interface external.
external impl as Printable = Song;
// Make the `Print` function from `Printable` available
// under the name `PrintToScreen`.
alias PrintToScreen = Printable.Print;
}
```
### Use case: Using independent libraries together
Imagine we have two packages that are developed independently. Package
`CompareLib` defines an interface `CompareLib.Comparable` and a generic
algorithm `CompareLib.Sort` that operates on types that implement
`CompareLib.Comparable`. Package `SongLib` defines a type `SongLib.Song`.
Neither has a dependency on the other, so neither package defines an
implementation for `CompareLib.Comparable` for type `SongLib.Song`. A user that
wants to pass a value of type `SongLib.Song` to `CompareLib.Sort` has to define
an adapter that provides an implementation of `CompareLib.Comparable` for
`SongLib.Song`. This adapter will probably use the
[`extends` facility of adapters](#extending-adapter) to preserve the
`SongLib.Song` API.
```
import CompareLib;
import SongLib;
adapter Song extends SongLib.Song {
impl as CompareLib.Comparable { ... }
}
// Or, to keep the names from CompareLib.Comparable out of Song's API:
adapter Song extends SongLib.Song { }
external impl Song as CompareLib.Comparable { ... }
// Or, equivalently:
adapter Song extends SongLib.Song {
external impl as CompareLib.Comparable { ... }
}
```
The caller can either convert `SongLib.Song` values to `Song` when calling
`CompareLib.Sort` or just start with `Song` values in the first place.
```
var lib_song: SongLib.Song = ...;
CompareLib.Sort((lib_song as Song,));
var song: Song = ...;
CompareLib.Sort((song,));
```
### Use case: Defining an impl for use by other types
Let's say we want to provide a possible implementation of an interface for use
by types for which that implementation would be appropriate. We can do that by
defining an adapter implementing the interface that is parameterized on the type
it is adapting. That impl may then be pulled in using the `impl as ... = ...;`
syntax.
For example, given an interface `Comparable` for deciding which value is
smaller:
```
interface Comparable {
fn Less[self: Self](rhs: Self) -> bool;
}
```
We might define an adapter that implements `Comparable` for types that define
another interface `Difference`:
```
interface Difference {
fn Sub[self: Self](rhs: Self) -> i32;
}
adapter ComparableFromDifference(T:! Difference) for T {
impl as Comparable {
fn Less[self: Self](rhs: Self) -> bool {
return (self as T).Sub(rhs) < 0;
}
}
}
class IntWrapper {
var x: i32;
impl as Difference {
fn Sub[self: Self](rhs: Self) -> i32 {
return left.x - right.x;
}
}
impl as Comparable = ComparableFromDifferenceFn(IntWrapper);
}
```
**TODO:** If we support function types, we could potentially pass a function to
use to the adapter instead:
```
adapter ComparableFromDifferenceFn
(T:! type, Difference:! fnty(T, T)->i32) for T {
impl as Comparable {
fn Less[self: Self](rhs: Self) -> bool {
return Difference(self, rhs) < 0;
}
}
}
class IntWrapper {
var x: i32;
fn Difference(left: Self, right: Self) {
return left.x - right.x;
}
impl as Comparable =
ComparableFromDifferenceFn(IntWrapper, Difference);
}
```
### Use case: Private impl
Adapter types can be used when a library publicly exposes a type, but only wants
to say that type implements an interface as a private detail internal to the
implementation of the type. In that case, instead of implementing the interface
for the public type, the library can create a private adapter for that type and
implement the interface on that instead. Any member of the class can cast its
`self` parameter to the adapter type when it wants to make use of the private
impl.
```
// Public, in API file
class Complex64 {
// ...
fn CloserToOrigin[self: Self](them: Self) -> bool;
}
// Private
adapter ByReal extends Complex64 {
// Complex numbers are not generally comparable,
// but this comparison function is useful for some
// method implementations.
impl as Comparable {
fn Less[self: Self](that: Self) -> bool {
return self.Real() < that.Real();
}
}
}
fn Complex64.CloserToOrigin[self: Self](them: Self) -> bool {
var me_mag: ByReal = self * self.Conj() as ByReal;
var them_mag: ByReal = them * them.Conj() as ByReal;
return me_mag.Less(them_mag);
}
```
### Use case: Accessing external names
Consider a case where a function will call several functions from an interface
that is [implemented externally](terminology.md#external-impl) for a type.
```
interface DrawingContext {
fn SetPen[self: Self](...);
fn SetFill[self: Self](...);
fn DrawRectangle[self: Self](...);
fn DrawLine[self: Self](...);
...
}
external impl Window as DrawingContext { ... }
```
An adapter can make that much more convenient by making a compatible type where
the interface is [implemented internally](terminology.md#internal-impl). This
avoids having to [qualify](terminology.md#qualified-member-access-expression)
each call to methods in the interface.
```
adapter DrawInWindow for Window {
impl as DrawingContext = Window;
}
fn Render(w: Window) {
let d: DrawInWindow = w as DrawInWindow;
d.SetPen(...);
d.SetFill(...);
d.DrawRectangle(...);
...
}
```
### Adapter with stricter invariants
**Future work:** Rust also uses the newtype idiom to create types with
additional invariants or other information encoded in the type
([1](https://doc.rust-lang.org/rust-by-example/generics/new_types.html),
[2](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction),
[3](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)).
This is used to record in the type system that some data has passed validation
checks, like `ValidDate` with the same data layout as `Date`. Or to record the
units associated with a value, such as `Seconds` versus `Milliseconds` or `Feet`
versus `Meters`. We should have some way of restricting the casts between a type
and an adapter to address this use case.
## Associated constants
In addition to associated methods, we allow other kinds of
[associated entities](terminology.md#associated-entity). For consistency, we use
the same syntax to describe a constant in an interface as in a type without
assigning a value. As constants, they are declared using the `let` introducer.
For example, a fixed-dimensional point type could have the dimension as an
associated constant.
```
interface NSpacePoint {
let N:! i32;
// The following require: 0 <= i < N.
fn Get[addr self: Self*](i: i32) -> f64;
fn Set[addr self: Self*](i: i32, value: f64);
// Associated constants may be used in signatures:
fn SetAll[addr self: Self*](value: Array(f64, N));
}
```
An implementation of an interface specifies values for associated constants with
a [`where` clause](#where-constraints). For example, implementations of
`NSpacePoint` for different types might have different values for `N`:
```
class Point2D {
impl as NSpacePoint where .N = 2 {
fn Get[addr self: Self*](i: i32) -> f64 { ... }
fn Set[addr self: Self*](i: i32, value: f64) { ... }
fn SetAll[addr self: Self*](value: Array(f64, 2)) { ... }
}
}
class Point3D {
impl as NSpacePoint where .N = 3 {
fn Get[addr self: Self*](i: i32) -> f64 { ... }
fn Set[addr self: Self*](i: i32, value: f64) { ... }
fn SetAll[addr self: Self*](value: Array(f64, 3)) { ... }
}
}
```
Multiple assignments to associated constants may be joined using the `and`
keyword. The list of assignments is subject to two restrictions:
- An implementation of an interface cannot specify a value for a
[`final`](#final-members) associated constant.
- If an associated constant doesn't have a
[default value](#interface-defaults), every implementation must specify its
value.
These values may be accessed as members of the type:
```
Assert(Point2D.N == 2);
Assert(Point3D.N == 3);
fn PrintPoint[PointT:! NSpacePoint](p: PointT) {
for (var i: i32 = 0; i < PointT.N; ++i) {
if (i > 0) { Print(", "); }
Print(p.Get(i));
}
}
fn ExtractPoint[PointT:! NSpacePoint](
p: PointT,
dest: Array(f64, PointT.N)*) {
for (var i: i32 = 0; i < PointT.N; ++i) {
(*dest)[i] = p.Get(i);
}
}
```
**Comparison with other languages:** This feature is also called
[associated constants in Rust](https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants).
**Aside:** In general, the use of `:!` here means these `let` declarations will
only have compile-time and not runtime storage associated with them.
### Associated class functions
To be consistent with normal
[class function](/docs/design/classes.md#class-functions) declaration syntax,
associated class functions are written using a `fn` declaration:
```
interface DeserializeFromString {
fn Deserialize(serialized: String) -> Self;
}
class MySerializableType {
var i: i32;
impl as DeserializeFromString {
fn Deserialize(serialized: String) -> Self {
return (.i = StringToInt(serialized));
}
}
}
var x: MySerializableType = MySerializableType.Deserialize("3");
fn Deserialize(T:! DeserializeFromString, serialized: String) -> T {
return T.Deserialize(serialized);
}
var y: MySerializableType = Deserialize(MySerializableType, "4");
```
This is instead of declaring an associated constant using `let` with a function
type.
Together associated methods and associated class functions are called
_associated functions_, much like together methods and class functions are
called [member functions](/docs/design/classes.md#member-functions).
## Associated types
Associated types are [associated entities](terminology.md#associated-entity)
that happen to be types. These are particularly interesting since they can be
used in the signatures of associated methods or functions, to allow the
signatures of methods to vary from implementation to implementation. We already
have one example of this: the `Self` type discussed
[in the "Interfaces" section](#interfaces). For other cases, we can say that the
interface declares that each implementation will provide a type under a specific
name. For example:
```
interface StackAssociatedType {
let ElementType:! type;
fn Push[addr self: Self*](value: ElementType);
fn Pop[addr self: Self*]() -> ElementType;
fn IsEmpty[addr self: Self*]() -> bool;
}
```
Here we have an interface called `StackAssociatedType` which defines two
methods, `Push` and `Pop`. The signatures of those two methods declare them as
accepting or returning values with the type `ElementType`, which any implementer
of `StackAssociatedType` must also define. For example, maybe `DynamicArray`
implements `StackAssociatedType`:
```
class DynamicArray(T:! type) {
class IteratorType { ... }
fn Begin[addr self: Self*]() -> IteratorType;
fn End[addr self: Self*]() -> IteratorType;
fn Insert[addr self: Self*](pos: IteratorType, value: T);
fn Remove[addr self: Self*](pos: IteratorType);
// Set the associated type `ElementType` to `T`.
impl as StackAssociatedType where .ElementType = T {
fn Push[addr self: Self*](value: ElementType) {
self->Insert(self->End(), value);
}
fn Pop[addr self: Self*]() -> ElementType {
var pos: IteratorType = self->End();
Assert(pos != self->Begin());
--pos;
returned var ret: ElementType = *pos;
self->Remove(pos);
return var;
}
fn IsEmpty[addr self: Self*]() -> bool {
return self->Begin() == self->End();
}
}
}
```
The keyword `Self` can be used after the `as` in an `impl` declaration as a
shorthand for the type being implemented, including in the `where` clause
specifying the values of associated types, as in:
```
external impl VeryLongTypeName as Add
// `Self` here means `VeryLongTypeName`
where .Result == Self {
...
}
```
**Alternatives considered:** See
[other syntax options considered in #731 for specifying associated types](/proposals/p0731.md#syntax-for-associated-constants).
In particular, it was deemed that
[Swift's approach of inferring the associated type from method signatures in the impl](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190)
was unneeded complexity.
The definition of the `StackAssociatedType` is sufficient for writing a generic
function that operates on anything implementing that interface, for example:
```
fn PeekAtTopOfStack[StackType:! StackAssociatedType](s: StackType*)
-> StackType.ElementType {
var top: StackType.ElementType = s->Pop();
s->Push(top);
return top;
}
```
Inside the generic function `PeekAtTopOfStack`, the `ElementType` associated
type member of `StackType` is erased. This means `StackType.ElementType` has the
API dictated by the declaration of `ElementType` in the interface
`StackAssociatedType`.
Outside the generic, associated types have the concrete type values determined
by impl lookup, rather than the erased version of that type used inside a
generic.
```
var my_array: DynamicArray(i32) = (1, 2, 3);
// PeekAtTopOfStack's `StackType` is set to `DynamicArray(i32)`
// with `StackType.ElementType` set to `i32`.
Assert(PeekAtTopOfStack(my_array) == 3);
```
This is another part of achieving
[the goal that generic functions can be used in place of regular functions without changing the return type that callers see](goals.md#path-from-regular-functions)
discussed in the [return type section](#return-type).
Associated types can also be implemented using a
[member type](/docs/design/classes.md#member-type).
```
interface Container {
let IteratorType:! Iterator;
...
}
class DynamicArray(T:! type) {
...
impl as Container {
class IteratorType {
impl Iterator { ... }
}
...
}
}
```
For context, see
["Interface type parameters and associated types" in the generics terminology document](terminology.md#interface-type-parameters-and-associated-types).
**Comparison with other languages:** Both
[Rust](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types)
and [Swift](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID189)
support associated types.
### Implementation model
The associated type can be modeled by a witness table field in the interface's
witness table.
```
interface Iterator {
fn Advance[addr self: Self*]();
}
interface Container {
let IteratorType:! Iterator;
fn Begin[addr self: Self*]() -> IteratorType;
}
```
is represented by:
```
class Iterator(Self:! type) {
var Advance: fnty(this: Self*);
...
}
class Container(Self:! type) {
// Representation type for the iterator.
let IteratorType:! type;
// Witness that IteratorType implements Iterator.
var iterator_impl: Iterator(IteratorType)*;
// Method
var Begin: fnty (this: Self*) -> IteratorType;
...
}
```
## Parameterized interfaces
Associated types don't change the fact that a type can only implement an
interface at most once.
If instead you want a family of related interfaces, one per possible value of a
type parameter, multiple of which could be implemented for a single type, you
would use
[parameterized interfaces](terminology.md#interface-type-parameters-and-associated-types).
To write a parameterized version of the stack interface, instead of using
associated types, write a parameter list after the name of the interface instead
of the associated type declaration:
```
interface StackParameterized(ElementType:! type) {
fn Push[addr self: Self*](value: ElementType);
fn Pop[addr self: Self*]() -> ElementType;
fn IsEmpty[addr self: Self*]() -> bool;
}
```
Then `StackParameterized(Fruit)` and `StackParameterized(Veggie)` would be
considered different interfaces, with distinct implementations.
```
class Produce {
var fruit: DynamicArray(Fruit);
var veggie: DynamicArray(Veggie);
impl as StackParameterized(Fruit) {
fn Push[addr self: Self*](value: Fruit) {
self->fruit.Push(value);
}
fn Pop[addr self: Self*]() -> Fruit {
return self->fruit.Pop();
}
fn IsEmpty[addr self: Self*]() -> bool {
return self->fruit.IsEmpty();
}
}
impl as StackParameterized(Veggie) {
fn Push[addr self: Self*](value: Veggie) {
self->veggie.Push(value);
}
fn Pop[addr self: Self*]() -> Veggie {
return self->veggie.Pop();
}
fn IsEmpty[addr self: Self*]() -> bool {
return self->veggie.IsEmpty();
}
}
}
```
Unlike associated types in interfaces and parameters to types, interface
parameters can't be deduced. For example, if we were to rewrite
[the `PeekAtTopOfStack` example in the "associated types" section](#associated-types)
for `StackParameterized(T)` it would generate a compile error:
```
// ❌ Error: can't deduce interface parameter `T`.
fn BrokenPeekAtTopOfStackParameterized
[T:! type, StackType:! StackParameterized(T)]
(s: StackType*) -> T { ... }
```
This error is because the compiler can not determine if `T` should be `Fruit` or
`Veggie` when passing in argument of type `Produce*`. The function's signature
would have to be changed so that the value for `T` could be determined from the
explicit parameters.
```
fn PeekAtTopOfStackParameterized
[T:! type, StackType:! StackParameterized(T)]
(s: StackType*, _:! singleton_type_of(T)) -> T { ... }
var produce: Produce = ...;
var top_fruit: Fruit =
PeekAtTopOfStackParameterized(&produce, Fruit);
var top_veggie: Veggie =
PeekAtTopOfStackParameterized(&produce, Veggie);
```
The pattern `_:! singleton_type_of(T)` is a placeholder syntax for an expression
that will only match `T`, until issue
[#578: Value patterns as function parameters](https://github.com/carbon-language/carbon-lang/issues/578)
is resolved. Using that pattern in the explicit parameter list allows us to make
`T` available earlier in the declaration so it can be passed as the argument to
the parameterized interface `StackParameterized`.
This approach is useful for the `ComparableTo(T)` interface, where a type might
be comparable with multiple other types, and in fact interfaces for
[operator overloads](#operator-overloading) more generally. Example:
```
interface EquatableWith(T:! type) {
fn Equals[self: Self](rhs: T) -> bool;
...
}
class Complex {
var real: f64;
var imag: f64;
// Can implement this interface more than once
// as long as it has different arguments.
impl as EquatableWith(f64) { ... }
// Same as: impl as EquatableWith(Complex) { ... }
impl as EquatableWith(Self) { ... }
}
```
All interface parameters must be marked as "generic", using the `:!` syntax.
This reflects these two properties of these parameters:
- They must be resolved at compile-time, and so can't be passed regular
dynamic values.
- We allow either generic or template values to be passed in.
**Note:** Interface parameters aren't required to be types, but that is the vast
majority of cases. As an example, if we had an interface that allowed a type to
define how the tuple-member-read operator would work, the index of the member
could be an interface parameter:
```
interface ReadTupleMember(index:! u32) {
let T:! type;
// Returns self[index]
fn Get[self: Self]() -> T;
}
```
This requires that the index be known at compile time, but allows different
indices to be associated with different types.
**Caveat:** When implementing an interface twice for a type, the interface
parameters are required to always be different. For example:
```
interface Map(FromType:! type, ToType:! type) {
fn Map[addr self: Self*](needle: FromType) -> Optional(ToType);
}
class Bijection(FromType:! type, ToType:! type) {
impl as Map(FromType, ToType) { ... }
impl as Map(ToType, FromType) { ... }
}
// ❌ Error: Bijection has twodifferent impl definitions of
// interface Map(String, String)
var oops: Bijection(String, String) = ...;
```
In this case, it would be better to have an [adapting type](#adapting-types) to
contain the `impl` for the reverse map lookup, instead of implementing the `Map`
interface twice:
```
class Bijection(FromType:! type, ToType:! type) {
impl as Map(FromType, ToType) { ... }
}
adapter ReverseLookup(FromType:! type, ToType:! type)
for Bijection(FromType, ToType) {
impl as Map(ToType, FromType) { ... }
}
```
**Comparison with other languages:** Rust calls
[traits with type parameters "generic traits"](https://doc.rust-lang.org/reference/items/traits.html#generic-traits)
and
[uses them for operator overloading](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#default-generic-type-parameters-and-operator-overloading).
[Rust uses the term "type parameters"](https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md#clearer-trait-matching)
for both interface type parameters and associated types. The difference is that
interface parameters are "inputs" since they _determine_ which `impl` to use,
and associated types are "outputs" since they are determined _by_ the `impl`,
but play no role in selecting the `impl`.
### Impl lookup
Let's say you have some interface `I(T, U(V))` being implemented for some type
`A(B(C(D), E))`. To satisfy the [orphan rule for coherence](#orphan-rule), that
`impl` must be defined in some library that must be imported in any code that
looks up whether that interface is implemented for that type. This requires that
`impl` is defined in the same library that defines the interface or one of the
names needed by the type. That is, the `impl` must be defined with one of `I`,
`T`, `U`, `V`, `A`, `B`, `C`, `D`, or `E`. We further require anything looking
up this `impl` to import the _definitions_ of all of those names. Seeing a
forward declaration of these names is insufficient, since you can presumably see
forward declarations without seeing an `impl` with the definition. This
accomplishes a few goals:
- The compiler can check that there is only one definition of any `impl` that
is actually used, avoiding
[One Definition Rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule)
problems.
- Every attempt to use an `impl` will see the exact same `impl`, making the
interpretation and semantics of code consistent no matter its context, in
accordance with the
[low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md).
- Allowing the `impl` to be defined with either the interface or the type
addresses the
[expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions).
Note that [the rules for specialization](#lookup-resolution-and-specialization)
do allow there to be more than one `impl` to be defined for a type, by
unambiguously picking one as most specific.
**References:** Implementation coherence is
[defined in terminology](terminology.md#coherence), and is
[a goal for Carbon](goals.md#coherence). More detail can be found in
[this appendix with the rationale and alternatives considered](appendix-coherence.md).
### Parameterized named constraints
We should also allow the [named constraint](#named-constraints) construct to
support parameters. Parameters would work the same way as for interfaces.
## Where constraints
So far, we have restricted a generic type parameter by saying it has to
implement an interface or a set of interfaces. There are a variety of other
constraints we would like to be able to express, such as applying restrictions
to its associated types and associated constants. This is done using the `where`
operator that adds constraints to a type-of-type.
The where operator can be applied to a type-of-type in a declaration context:
```
// Constraints on function parameters:
fn F[V:! D where ...](v: V) { ... }
// Constraints on a class parameter:
class S(T:! B where ...) {
// Constraints on a method:
fn G[self: Self, V:! D where ...](v: V);
}
// Constraints on an interface parameter:
interface A(T:! B where ...) {
// Constraints on an associated type:
let U:! C where ...;
// Constraints on an associated method:
fn G[self: Self, V:! D where ...](v: V);
}
```
We also allow you to name constraints using a `where` operator in a `let` or
`constraint` definition. The expressions that can follow the `where` keyword are
described in the ["constraint use cases"](#constraint-use-cases) section, but
generally look like boolean expressions that should evaluate to `true`.
The result of applying a `where` operator to a type-of-type is another
type-of-type. Note that this expands the kinds of requirements that
type-of-types can have from just interface requirements to also include the
various kinds of constraints discussed later in this section. In addition, it
can introduce relationships between different type variables, such as that a
member of one is equal to the member of another. The `where` operator is not
associative, so a type expression using multiple must use round parens `(`...`)`
to specify grouping.
**Comparison with other languages:** Both Swift and Rust use `where` clauses on
declarations instead of in the expression syntax. These happen after the type
that is being constrained has been given a name and use that name to express the
constraint.
Rust also supports
[directly passing in the values for associated types](https://rust-lang.github.io/rfcs/0195-associated-items.html#constraining-associated-types)
when using a trait as a constraint. This is helpful when specifying concrete
types for all associated types in a trait in order to
[make it object safe so it can be used to define a trait object type](https://rust-lang.github.io/rfcs/0195-associated-items.html#trait-objects).
Rust is adding trait aliases
([RFC](https://github.com/rust-lang/rfcs/blob/master/text/1733-trait-alias.md),
[tracking issue](https://github.com/rust-lang/rust/issues/41517)) to support
naming some classes of constraints.
### Constraint use cases
#### Set an associated constant to a specific value
We might need to write a function that only works with a specific value of an
[associated constant](#associated-constants) `N`. In this case, the name of the
associated constant is written after a `.`, followed by an `=`, and then the
value:
```
fn PrintPoint2D[PointT:! NSpacePoint where .N = 2](p: PointT) {
Print(p.Get(0), ", ", p.Get(1));
}
```
Similarly in an interface definition:
```
interface Has2DPoint {
let PointT:! NSpacePoint where .N = 2;
}
```
The "dot followed by the name of a member" construct, `.N` in the examples
above, is called a _designator_. A designator refers to the value of that member
for whatever type is to satisfy this constraint.
To name such a constraint, you may use a `let` or a `constraint` declaration:
```
let Point2DInterface:! auto = NSpacePoint where .N = 2;
constraint Point2DInterface {
extends NSpacePoint where .N = 2;
}
```
This syntax is also used to specify the values of
[associated constants](#associated-constants) when implementing an interface for
a type.
**Concern:** Using `=` for this use case is not consistent with other `where`
clauses that write a boolean expression that evaluates to `true` when the
constraint is satisfied.
A constraint to say that two associated constants should have the same value
without specifying what specific value they should have must use `==` instead of
`=`:
```
interface PointCloud {
let Dim:! i32;
let PointT:! NSpacePoint where .N == Dim;
}
```
#### Same type constraints
##### Set an associated type to a specific value
Functions accepting a generic type might also want to constrain one of its
associated types to be a specific, concrete type. For example, we might want to
have a function only accept stacks containing integers:
```
fn SumIntStack[T:! Stack where .ElementType = i32](s: T*) -> i32 {
var sum: i32 = 0;
while (!s->IsEmpty()) {
// s->Pop() has type `T.ElementType` == i32:
sum += s->Pop();
}
return sum;
}
```
To name these sorts of constraints, we could use `let` declarations or
`constraint` definitions:
```
let IntStack:! auto = Stack where .ElementType = i32;
constraint IntStack {
extends Stack where .ElementType = i32;
}
```
This syntax is also used to specify the values of
[associated types](#associated-types) when implementing an interface for a type.
##### Equal generic types
Alternatively, two generic types could be constrained to be equal to each other,
without specifying what that type is. This uses `==` instead of `=`. For
example, we could make the `ElementType` of an `Iterator` interface equal to the
`ElementType` of a `Container` interface as follows:
```
interface Iterator {
let ElementType:! type;
...
}
interface Container {
let ElementType:! type;
let IteratorType:! Iterator where .ElementType == ElementType;
...
}
```
Given an interface with two associated types
```
interface PairInterface {
let Left:! type;
let Right:! type;
}
```
we can constrain them to be equal in a function signature:
```
fn F[MatchedPairType:! PairInterface where .Left == .Right]
(x: MatchedPairType*);
```
or in an interface definition:
```
interface HasEqualPair {
let P:! PairInterface where .Left == .Right;
}
```
This kind of constraint can be named:
```
let EqualPair:! auto =
PairInterface where .Left == .Right;
constraint EqualPair {
extends PairInterface where .Left == .Right;
}
```
Another example of same type constraints is when associated types of two
different interfaces are constrained to be equal:
```
fn Map[CT:! Container,
FT:! Function where .InputType == CT.ElementType]
(c: CT, f: FT) -> Vector(FT.OutputType);
```
###### Satisfying both type-of-types
If the two types being constrained to be equal have been declared with different
type-of-types, then the actual type value they are set to will have to satisfy
both constraints. For example, if `SortedContainer.ElementType` is declared to
be `Comparable`, then in this declaration:
```
fn Contains
[SC:! SortedContainer,
CT:! Container where .ElementType == SC.ElementType]
(haystack: SC, needles: CT) -> bool;
```
the `where` constraint means `CT.ElementType` must satisfy `Comparable` as well.
However, inside the body of `Contains`, `CT.ElementType` will only act like the
implementation of `Comparable` is [external](#external-impl). That is, items
from the `needles` container won't directly have a `Compare` method member, but
can still be implicitly converted to `Comparable` and can still call `Compare`
using the compound member access syntax, `needle.(Comparable.Compare)(elt)`. The
rule is that an `==` `where` constraint between two type variables does not
modify the set of member names of either type. (If you write
`where .ElementType = String` with a `=` and a concrete type, then
`.ElementType` is actually set to `String` including the complete `String` API.)
Note that `==` constraints are symmetric, so the previous declaration of
`Contains` is equivalent to an alternative declaration where `CT` is declared
first and the `where` clause is attached to `SortedContainer`:
```
fn Contains
[CT:! Container,
SC:! SortedContainer where .ElementType == CT.ElementType]
(haystack: SC, needles: CT) -> bool;
```
#### Type bound for associated type
A `where` clause can express that a type must implement an interface. This is
more flexible than the usual approach of including that interface in the type
since it can be applied to associated type members as well.
##### Type bounds on associated types in declarations
In the following example, normally the `ElementType` of a `Container` can be any
type. The `SortContainer` function, however, takes a pointer to a type
satisfying `Container` with the additional constraint that its `ElementType`
must satisfy the `Comparable` interface, using an `impls` constraint:
```
interface Container {
let ElementType:! type;
...
}
fn SortContainer
[ContainerType:! Container where .ElementType impls Comparable]
(container_to_sort: ContainerType*);
```
In contrast to [a same type constraint](#same-type-constraints), this does not
say what type `ElementType` exactly is, just that it must satisfy some
type-of-type.
**Note:** `Container` defines `ElementType` as having type `type`, but
`ContainerType.ElementType` has type `Comparable`. This is because
`ContainerType` has type `Container where .ElementType impls Comparable`, not
`Container`. This means we need to be a bit careful when talking about the type
of `ContainerType` when there is a `where` clause modifying it.
##### Type bounds on associated types in interfaces
Given these definitions (omitting `ElementType` for brevity):
```
interface IteratorInterface { ... }
interface ContainerInterface {
let IteratorType:! IteratorInterface;
...
}
interface RandomAccessIterator {
extends IteratorInterface;
...
}
```
We can then define a function that only accepts types that implement
`ContainerInterface` where its `IteratorType` associated type implements
`RandomAccessIterator`:
```
fn F[ContainerType:! ContainerInterface
where .IteratorType impls RandomAccessIterator]
(c: ContainerType);
```
We would like to be able to name this constraint, defining a
`RandomAccessContainer` to be a type-of-type whose types satisfy
`ContainerInterface` with an `IteratorType` satisfying `RandomAccessIterator`.
```
let RandomAccessContainer:! auto =
ContainerInterface where .IteratorType impls RandomAccessIterator;
// or
constraint RandomAccessContainer {
extends ContainerInterface
where .IteratorType impls RandomAccessIterator;
}
// With the above definition:
fn F[ContainerType:! RandomAccessContainer](c: ContainerType);
// is equivalent to:
fn F[ContainerType:! ContainerInterface
where .IteratorType impls RandomAccessIterator]
(c: ContainerType);
```
#### Combining constraints
Constraints can be combined by separating constraint clauses with the `and`
keyword. This example expresses a constraint that two associated types are equal
and satisfy an interface:
```
fn EqualContainers
[CT1:! Container,
CT2:! Container where .ElementType impls HasEquality
and .ElementType == CT1.ElementType]
(c1: CT1*, c2: CT2*) -> bool;
```
**Comparison with other languages:** Swift and Rust use commas `,` to separate
constraint clauses, but that only works because they place the `where` in a
different position in a declaration. In Carbon, the `where` is attached to a
type in a parameter list that is already using commas to separate parameters.
#### Recursive constraints
We sometimes need to constrain a type to equal one of its associated types. In
this first example, we want to represent the function `Abs` which will return
`Self` for some but not all types, so we use an associated type `MagnitudeType`
to encode the return type:
```
interface HasAbs {
extends Numeric;
let MagnitudeType:! Numeric;
fn Abs[self: Self]() -> MagnitudeType;
}
```
For types representing subsets of the real numbers, such as `i32` or `f32`, the
`MagnitudeType` will match `Self`, the type implementing an interface. For types
representing complex numbers, the types will be different. For example, the
`Abs()` applied to a `Complex64` value would produce a `f32` result. The goal is
to write a constraint to restrict to the first case.
In a second example, when you take the slice of a type implementing `Container`
you get a type implementing `Container` which may or may not be the same type as
the original container type. However, taking the slice of a slice always gives
you the same type, and some functions want to only operate on containers whose
slice type is the same as the container type.
To solve this problem, we think of `Self` as an actual associated type member of
every interface. We can then address it using `.Self` in a `where` clause, like
any other associated type member.
```
fn Relu[T:! HasAbs where .MagnitudeType == .Self](x: T) {
// T.MagnitudeType == T so the following is allowed:
return (x.Abs() + x) / 2;
}
fn UseContainer[T:! Container where .SliceType == .Self](c: T) -> bool {
// T.SliceType == T so `c` and `c.Slice(...)` can be compared:
return c == c.Slice(...);
}
```
Notice that in an interface definition, `Self` refers to the type implementing
this interface while `.Self` refers to the associated type currently being
defined.
```
interface Container {
let ElementType:! type;
let SliceType:! Container
where .ElementType == ElementType and
.SliceType == .Self;
fn GetSlice[addr self: Self*]
(start: IteratorType, end: IteratorType) -> SliceType;
}
```
These recursive constraints can be named:
```
let RealAbs:! auto = HasAbs where .MagnitudeType == .Self;
constraint RealAbs {
extends HasAbs where .MagnitudeType == Self;
}
let ContainerIsSlice:! auto =
Container where .SliceType == .Self;
constraint ContainerIsSlice {
extends Container where .SliceType == Self;
}
```
Note that using the `constraint` approach we can name these constraints using
`Self` instead of `.Self`, since they refer to the same type.
The `.Self` construct follows these rules:
- `X :!` introduces `.Self:! type`, where references to `.Self` are resolved
to `X`. This allows you to use `.Self` as an interface parameter as in
`X:! I(.Self)`.
- `A where` introduces `.Self:! A` and `.Foo` for each member `Foo` of `A`
- It's an error to reference `.Self` if it refers to more than one different
thing or isn't a type.
- You get the innermost, most-specific type for `.Self` if it is introduced
twice in a scope. By the previous rule, it is only legal if they all refer
to the same generic parameter.
So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the
`where`. This is allowed since both times it means `X`. After the `:!`, `.Self`
has the type `type`, which gets refined to `A` after the `where`. In contrast,
it is an error if `.Self` could mean two different things, as in:
```
// ❌ Illegal: `.Self` could mean `T` or `T.A`.
fn F[T:! InterfaceA where .A impls
(InterfaceB where .B == .Self)](x: T);
```
#### Parameterized type implements interface
There are times when a function will pass a generic type parameter of the
function as an argument to a parameterized type, as in the previous case, and in
addition the function needs the result to implement a specific interface.
```
// Some parameterized type.
class Vector(T:! type) { ... }
// Parameterized type implements interface only for some arguments.
external impl Vector(String) as Printable { ... }
// Constraint: `T` such that `Vector(T)` implements `Printable`
fn PrintThree
[T:! type where Vector(.Self) impls Printable]
(a: T, b: T, c: T) {
var v: Vector(T) = (a, b, c);
Print(v);
}
```
**Comparison with other languages:** This use case was part of the
[Rust rationale for adding support for `where` clauses](https://rust-lang.github.io/rfcs/0135-where.html#motivation).
#### Another type implements parameterized interface
In this case, we need some other type to implement an interface parameterized by
a generic type parameter. The syntax for this case follows the previous case,
except now the `.Self` parameter is on the interface to the right of the
`impls`. For example, we might need a type parameter `T` to support explicit
conversion from an integer type like `i32`:
```
interface As(T:! type) {
fn Convert[self: Self]() -> T;
}
fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T {
return x * (2 as T);
}
```
### Constraints must use a designator
We don't allow a `where` constraint unless it applies a restriction to the
current type. This means referring to some
[designator](#set-an-associated-constant-to-a-specific-value), like
`.MemberName`, or [`.Self`](#recursive-constraints). Examples:
- `Container where .ElementType = i32`
- `type where Vector(.Self) impls Sortable`
- `Addable where i32 impls AddableWith(.Result)`
Constraints that only refer to other types should be moved to the type that is
declared last. So:
```carbon
// ❌ Error: `where A == B` does not use `.Self` or a designator
fn F[A:! type, B:! type, C:! type where A == B](a: A, b: B, c: C);
```
must be replaced by:
```carbon
// ✅ Allowed
fn F[A:! type, B:! type where A == .Self, C:! type](a: A, b: B, c: C);
```
This includes `where` clauses used in an `impl` declaration:
```
// ❌ Error: `where T impls B` does not use `.Self` or a designator
external impl forall [T:! type] T as A where T impls B {}
// ✅ Allowed
external impl forall [T:! type where .Self impls B] T as A {}
// ✅ Allowed
external impl forall [T:! B] T as A {}
```
This clarifies the meaning of the `where` clause and reduces the number of
redundant ways to express a restriction, following the
[one-way principle](/docs/project/principles/one_way.md).
**Alternative considered:** This rule was added in proposal
[#2376](https://github.com/carbon-language/carbon-lang/pull/2376), which
[considered whether this rule should be added](/proposals/p2376.md#alternatives-considered).
### Implied constraints
Imagine we have a generic function that accepts an arbitrary `HashMap`:
```
fn LookUp[KeyType:! type](hm: HashMap(KeyType, i32)*,
k: KeyType) -> i32;
fn PrintValueOrDefault[KeyType:! Printable,
ValueT:! Printable & HasDefault]
(map: HashMap(KeyType, ValueT), key: KeyT);
```
The `KeyType` in these declarations does not visibly satisfy the requirements of
`HashMap`, which requires the type implement `Hashable` and other interfaces:
```
class HashMap(
KeyType:! Hashable & EqualityComparable & Movable,
...) { ... }
```
In this case, `KeyType` gets `Hashable` and so on as _implied constraints_.
Effectively that means that these functions are automatically rewritten to add a
`where` constraint on `KeyType` attached to the `HashMap` type:
```
fn LookUp[KeyType:! type]
(hm: HashMap(KeyType, i32)*
where KeyType impls Hashable & EqualityComparable & Movable,
k: KeyType) -> i32;
fn PrintValueOrDefault[KeyType:! Printable,
ValueT:! Printable & HasDefault]
(map: HashMap(KeyType, ValueT)
where KeyType impls Hashable & EqualityComparable & Movable,
key: KeyT);
```
In this case, Carbon will accept the definition and infer the needed constraints
on the generic type parameter. This is both more concise for the author of the
code and follows the
["don't repeat yourself" principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
This redundancy is undesirable since it means if the needed constraints for
`HashMap` are changed, then the code has to be updated in more locations.
Further it can add noise that obscures relevant information. In practice, any
user of these functions will have to pass in a valid `HashMap` instance, and so
will have already satisfied these constraints.
This implied constraint is equivalent to the explicit constraint that each
parameter and return type [is legal](#must-be-legal-type-argument-constraints).
**Note:** These implied constraints affect the _requirements_ of a generic type
parameter, but not its _member names_. This way you can always look at the
declaration to see how name resolution works, without having to look up the
definitions of everything it is used as an argument to.
**Limitation:** To limit readability concerns and ambiguity, this feature is
limited to a single signature. Consider this interface declaration:
```
interface GraphNode {
let Edge:! type;
fn EdgesFrom[self: Self]() -> HashSet(Edge);
}
```
One approach would be to say the use of `HashSet(Edge)` in the signature of the
`EdgesFrom` function would imply that `Edge` satisfies the requirements of an
argument to `HashSet`, such as being `Hashable`. Another approach would be to
say that the `EdgesFrom` would only be conditionally available when `Edge` does
satisfy the constraints on `HashSet` arguments. Instead, Carbon will reject this
definition, requiring the user to include all the constraints required for the
other declarations in the interface in the declaration of the `Edge` associated
type. Similarly, a parameter to a class must be declared with all the
constraints needed to declare the members of the class that depend on that
parameter.
**Comparison with other languages:** Both Swift
([1](https://www.swiftbysundell.com/tips/inferred-generic-type-constraints/),
[2](https://github.com/apple/swift/blob/main/docs/Generics.rst#constraint-inference))
and
[Rust](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0b2d645bd205f24a7a6e2330d652c32e)
support some form of this feature as part of their type inference (and
[the Rust community is considering expanding support](http://smallcultfollowing.com/babysteps//blog/2022/04/12/implied-bounds-and-perfect-derive/#expanded-implied-bounds)).
#### Must be legal type argument constraints
Now consider the case that the generic type parameter is going to be used as an
argument to a parameterized type in a function body, not in the signature. If
the parameterized type was explicitly mentioned in the signature, the implied
constraint feature would ensure all of its requirements were met. The developer
can create a trivial
[parameterized type implements interface](#parameterized-type-implements-interface)
`where` constraint to just say the type is a legal with this argument, by saying
that the parameterized type implements `type`, which all types do.
For example, a function that adds its parameters to a `HashSet` to deduplicate
them, needs them to be `Hashable` and so on. To say "`T` is a type where
`HashSet(T)` is legal," we can write:
```
fn NumDistinct[T:! type where HashSet(.Self) impls type]
(a: T, b: T, c: T) -> i32 {
var set: HashSet(T);
set.Add(a);
set.Add(b);
set.Add(c);
return set.Size();
}
```
This has the same advantages over repeating the constraints on `HashSet`
arguments in the type of `T` as the general implied constraints above.
### Referencing names in the interface being defined
The constraint in a `where` clause is required to only reference earlier names
from this scope, as in this example:
```
interface Graph {
let E: Edge;
let V: Vert where .E == E and .Self == E.V;
}
```
### Manual type equality
Imagine we have some function with generic parameters:
```
fn F[T:! SomeInterface](x: T) {
x.G(x.H());
}
```
We want to know if the return type of method `T.H` is the same as the parameter
type of `T.G` in order to typecheck the function. However, determining whether
two type expressions are transitively equal is in general undecidable, as
[has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024).
Carbon's approach is to only allow implicit conversions between two type
expressions that are constrained to be equal in a single where clause. This
means that if two type expressions are only transitively equal, the user will
need to include a sequence of casts or use an
[`observe` declaration](#observe-declarations) to convert between them.
Given this interface `Transitive` that has associated types that are constrained
to all be equal, with interfaces `P`, `Q`, and `R`:
```
interface P { fn InP[self: Self](); }
interface Q { fn InQ[self: Self](); }
interface R { fn InR[self: Self](); }
interface Transitive {
let A:! P;
let B:! Q where .Self == A;
let C:! R where .Self == B;
fn GetA[self: Self]() -> A;
fn TakesC[self: Self](c: C);
}
```
A cast to `B` is needed to call `TakesC` with a value of type `A`, so each step
only relies on one equality:
```
fn F[T:! Transitive](t: T) {
// ✅ Allowed
t.TakesC(t.GetA() as T.B);
// ✅ Allowed
let b: T.B = t.GetA();
t.TakesC(b);
// ❌ Not allowed: t.TakesC(t.GetA());
}
```
A value of type `A`, such as the return value of `GetA()`, has the API of `P`.
Any such value also implements `Q`, and since the compiler can see that by way
of a single `where` equality, values of type `A` are treated as if they
implement `Q` [externally](terminology.md#external-impl). However, the compiler
will require a cast to `B` or `C` to see that the type implements `R`.
```
fn TakesPQR[U:! P & Q & R](u: U);
fn G[T:! Transitive](t: T) {
var a: T.A = t.GetA();
// ✅ Allowed: `T.A` implements `P`.
a.InP();
// ✅ Allowed: `T.A` implements `Q` externally.
a.(Q.InQ)();
// ❌ Not allowed: a.InQ();
// ✅ Allowed: values of type `T.A` may be cast
// to `T.B`, which implements `Q` internally.
(a as T.B).InQ();
// ✅ Allowed: `T.B` implements `R` externally.
(a as T.B).(R.InR)();
// ❌ Not allowed: TakesPQR(a);
// ✅ Allowed: `T.B` implements `P`, `Q`, and
// `R`, though the implementations of `P`
// and `R` are external.
TakesPQR(a as T.B);
}
```
The compiler may have several different `where` clauses to consider,
particularly when an interface has associated types that recursively satisfy the
same interface. For example, given this interface `Commute`:
```
interface Commute {
let X:! Commute;
let Y:! Commute where .X == X.Y;
fn GetX[self: Self]() -> X;
fn GetY[self: Self]() -> Y;
fn TakesXXY[self: Self](xxy: X.X.Y);
}
```
and a function `H` taking a value with some type implementing this interface,
then the following would be legal statements in `H`:
```
fn H[C: Commute](c: C) {
// ✅ Legal: argument has type `C.X.X.Y`
c.TakesXXY(c.GetX().GetX().GetY());
// ✅ Legal: argument has type `C.X.Y.X` which is equal
// to `C.X.X.Y` following only one `where` clause.
c.TakesXXY(c.GetX().GetY().GetX());
// ✅ Legal: cast is legal since it matches a `where`
// clause, and produces an argument that has type
// `C.X.Y.X`.
c.TakesXXY(c.GetY().GetX().GetX() as C.X.Y.X);
}
```
That last call would not be legal without the cast, though.
**Comparison with other languages:** Other languages such as Swift and Rust
instead perform automatic type equality. In practice this means that their
compiler can reject some legal programs based on heuristics simply to avoid
running for an unbounded length of time.
The benefits of the manual approach include:
- fast compilation, since the compiler does not need to explore a potentially
large set of combinations of equality restrictions, supporting
[Carbon's goal of fast and scalable development](/docs/project/goals.md#fast-and-scalable-development);
- expressive and predictable semantics, since there are no limitations on how
complex a set of constraints can be supported; and
- simplicity.
The main downsides are:
- manual work for the source code author to prove to the compiler that types
are equal; and
- verbosity.
We expect that rich error messages and IDE tooling will be able to suggest
changes to the source code when a single equality constraint is not sufficient
to show two type expressions are equal, but a more extensive automated search
can find a sequence that prove they are equal.
#### `observe` declarations
An `observe` declaration lists a sequence of type expressions that are equal by
some same-type `where` constraints. These `observe` declarations may be included
in an `interface` definition or a function body, as in:
```
interface Commute {
let X:! Commute;
let Y:! Commute where .X == X.Y;
...
observe X.X.Y == X.Y.X == Y.X.X;
}
fn H[C: Commute](c: C) {
observe C.X.Y.Y == C.Y.X.Y == C.Y.Y.X;
...
}
```
Every type expression after the first must be equal to some earlier type
expression in the sequence by a single `where` equality constraint. In this
example,
```
interface Commute {
let X:! Commute;
let Y:! Commute where .X == X.Y;
...
// ✅ Legal:
observe X.X.Y.Y == X.Y.X.Y == Y.X.X.Y == X.Y.Y.X;
}
```
the expression `X.Y.Y.X` is one equality away from `X.Y.X.Y` and so it is
allowed. This is even though `X.Y.X.Y` isn't the type expression immediately
prior to `X.Y.Y.X`.
After an `observe` declaration, all of the listed type expressions are
considered equal to each other using a single `where` equality. In this example,
the `observe` declaration in the `Transitive` interface definition provides the
link between associated types `A` and `C` that allows function `F` to type
check.
```
interface P { fn InP[self: Self](); }
interface Q { fn InQ[self: Self](); }
interface R { fn InR[self: Self](); }
interface Transitive {
let A:! P;
let B:! Q where .Self == A;
let C:! R where .Self == B;
fn GetA[self: Self]() -> A;
fn TakesC[self: Self](c: C);
// Without this `observe` declaration, the
// calls in `F` below would not be allowed.
observe A == B == C;
}
fn TakesPQR[U:! P & Q & R](u: U);
fn F[T:! Transitive](t: T) {
var a: T.A = t.GetA();
// ✅ Allowed: `T.A` == `T.C`
t.TakesC(a);
a.(R.InR());
// ✅ Allowed: `T.A` implements `P`,
// `T.A` == `T.B` that implements `Q`, and
// `T.A` == `T.C` that implements `R`.
TakesPQR(a);
}
```
Since adding an `observe` declaration only adds external implementations of
interfaces to generic types, they may be added without breaking existing code.
## Other constraints as type-of-types
There are some constraints that we will naturally represent as named
type-of-types. These can either be used directly to constrain a generic type
parameter, or in a `where ... impls ...` clause to constrain an associated type.
The compiler determines which types implement these interfaces, developers can
not explicitly implement these interfaces for their own types.
**Open question:** Are these names part of the prelude or in a standard library?
### Is a derived class
Given a type `T`, `Extends(T)` is a type-of-type whose values are types that are
derived from `T`. That is, `Extends(T)` is the set of all types `U` that are
subtypes of `T`.
```
fn F[T:! Extends(BaseType)](p: T*);
fn UpCast[T:! type](p: T*, U:! type where T impls Extends(.Self)) -> U*;
fn DownCast[T:! type](p: T*, U:! Extends(T)) -> U*;
```
**Open question:** Alternatively, we could define a new `extends` operator:
```
fn F[T:! type where .Self extends BaseType](p: T*);
fn UpCast[T:! type](p: T*, U:! type where T extends .Self) -> U*;
fn DownCast[T:! type](p: T*, U:! type where .Self extends T) -> U*;
```
**Comparison to other languages:** In Swift, you can
[add a required superclass to a type bound using `&`](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID282).
### Type compatible with another type
Given a type `U`, define the type-of-type `CompatibleWith(U)` as follows:
> `CompatibleWith(U)` is a type whose values are types `T` such that `T` and `U`
> are [compatible](terminology.md#compatible-types). That is values of types `T`
> and `U` can be cast back and forth without any change in representation (for
> example `T` is an [adapter](#adapting-types) for `U`).
To support this, we extend the requirements that type-of-types are allowed to
have to include a "data representation requirement" option.
`CompatibleWith` determines an equivalence relationship between types.
Specifically, given two types `T1` and `T2`, they are equivalent if
`T1 impls CompatibleWith(T2)`. That is, if `T1` has the type
`CompatibleWith(T2)`.
**Note:** Just like interface parameters, we require the user to supply `U`,
they may not be deduced. Specifically, this code would be illegal:
```
fn Illegal[U:! type, T:! CompatibleWith(U)](x: T*) ...
```
In general there would be multiple choices for `U` given a specific `T` here,
and no good way of picking one. However, similar code is allowed if there is
another way of determining `U`:
```
fn Allowed[U:! type, T:! CompatibleWith(U)](x: U*, y: T*) ...
```
#### Same implementation restriction
In some cases, we need to restrict to types that implement certain interfaces
the same way as the type `U`.
> The values of type `CompatibleWith(U, TT)` are types satisfying
> `CompatibleWith(U)` that have the same implementation of `TT` as `U`.
For example, if we have a type `HashSet(T)`:
```
class HashSet(T:! Hashable) { ... }
```
Then `HashSet(T)` may be cast to `HashSet(U)` if
`T impls CompatibleWith(U, Hashable)`. The one-parameter interpretation of
`CompatibleWith(U)` is recovered by letting the default for the second `TT`
parameter be `type`.
#### Example: Multiple implementations of the same interface
This allows us to represent functions that accept multiple implementations of
the same interface for a type.
```
enum CompareResult { Less, Equal, Greater }
interface Comparable {
fn Compare[self: Self](rhs: Self) -> CompareResult;
}
fn CombinedLess[T:! type](a: T, b: T,
U:! CompatibleWith(T) & Comparable,
V:! CompatibleWith(T) & Comparable) -> bool {
match ((a as U).Compare(b as U)) {
case CompareResult.Less => { return True; }
case CompareResult.Greater => { return False; }
case CompareResult.Equal => {
return (a as V).Compare(b as V) == CompareResult.Less;
}
}
}
```
Used as:
```
class Song { ... }
adapter SongByArtist for Song { impl as Comparable { ... } }
adapter SongByTitle for Song { impl as Comparable { ... } }
var s1: Song = ...;
var s2: Song = ...;
assert(CombinedLess(s1, s2, SongByArtist, SongByTitle) == True);
```
We might generalize this to a list of implementations:
```
fn CombinedCompare[T:! type]
(a: T, b: T, CompareList:! List(CompatibleWith(T) & Comparable))
-> CompareResult {
for (let U:! auto in CompareList) {
var result: CompareResult = (a as U).Compare(b);
if (result != CompareResult.Equal) {
return result;
}
}
return CompareResult.Equal;
}
assert(CombinedCompare(Song(...), Song(...), (SongByArtist, SongByTitle)) ==
CompareResult.Less);
```
**Open question:** How are compile-time lists of types declared and iterated
through? They will also be needed for
[variadic argument support](#variadic-arguments).
#### Example: Creating an impl out of other implementations
And then to package this functionality as an implementation of `Comparable`, we
combine `CompatibleWith` with [type adaptation](#adapting-types):
```
adapter ThenCompare(
T:! type,
CompareList:! List(CompatibleWith(T) & Comparable))
for T {
impl as Comparable {
fn Compare[self: Self](rhs: Self) -> CompareResult {
for (let U:! auto in CompareList) {
var result: CompareResult = (self as U).Compare(rhs as U);
if (result != CompareResult.Equal) {
return result;
}
}
return CompareResult.Equal;
}
}
}
let SongByArtistThenTitle: auto =
ThenCompare(Song, (SongByArtist, SongByTitle));
var s1: Song = ...;
var s2: SongByArtistThenTitle =
Song(...) as SongByArtistThenTitle;
assert((s1 as SongByArtistThenTitle).Compare(s2) ==
CompareResult.Less);
```
### Sized types and type-of-types
What is the size of a type?
- It could be fully known and fixed at compile time -- this is true of
primitive types (`i32`, `f64`, and so on), most
[classes](/docs/design/classes.md), and most other concrete types.
- It could be known generically. This means that it will be known at codegen
time, but not at type-checking time.
- It could be dynamic. For example, it could be a
[dynamic type](#runtime-type-fields), a slice, variable-sized type (such as
[found in Rust](https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts)),
or you could dereference a pointer to a base class that could actually point
to a [derived class](/docs/design/classes.md#inheritance).
- It could be unknown which category the type is in. In practice this will be
essentially equivalent to having dynamic size.
A type is called _sized_ if it is in the first two categories, and _unsized_
otherwise. Note: something with size 0 is still considered "sized". The
type-of-type `Sized` is defined as follows:
> `Sized` is a type whose values are types `T` that are "sized" -- that is the
> size of `T` is known, though possibly only generically.
Knowing a type is sized is a precondition to declaring variables of that type,
taking values of that type as parameters, returning values of that type, and
defining arrays of that type. Users will not typically need to express the
`Sized` constraint explicitly, though, since it will usually be a dependency of
some other constraint the type will need such as `Movable` or `Concrete`.
**Note:** The compiler will determine which types are "sized", this is not
something types will implement explicitly like ordinary interfaces.
Example:
```
// In the Carbon standard library
interface DefaultConstructible {
// Types must be sized to be default constructible.
impl as Sized;
fn Default() -> Self;
}
// Classes are "sized" by default.
class Name {
impl as DefaultConstructible {
fn Default() -> Self { ... }
}
...
}
fn F[T:! type](x: T*) { // T is unsized.
// ✅ Allowed: may access unsized values through a pointer.
var y: T* = x;
// ❌ Illegal: T is unsized.
var z: T;
}
// T is sized, but its size is only known generically.
fn G[T: DefaultConstructible](x: T*) {
// ✅ Allowed: T is default constructible, which means sized.
var y: T = T.Default();
}
var z: Name = Name.Default();;
// ✅ Allowed: `Name` is sized and implements `DefaultConstructible`.
G(&z);
```
**Open question:** Even if the size is fixed, it won't be known at the time of
compiling the generic function if we are using the dynamic strategy. Should we
automatically
[box](<https://en.wikipedia.org/wiki/Object_type_(object-oriented_programming)#Boxing>)
local variables when using the dynamic strategy? Or should we only allow
`MaybeBox` values to be instantiated locally? Or should this just be a case
where the compiler won't necessarily use the dynamic strategy?
**Open question:** Should the `Sized` type-of-type expose an associated constant
with the size? So you could say `T.ByteSize` in the above example to get a
generic int value with the size of `T`. Similarly you might say `T.ByteStride`
to get the number of bytes used for each element of an array of `T`.
#### Implementation model
This requires a special integer field be included in the witness table type to
hold the size of the type. This field will only be known generically, so if its
value is used for type checking, we need some way of evaluating those type tests
symbolically.
### `TypeId`
There are some capabilities every type can provide. For example, every type
should be able to return its name or identify whether it is equal to another
type. It is rare, however, for code to need to access these capabilities, so we
relegate these capabilities to an interface called `TypeId` that all types
automatically implement. This way generic code can indicate that it needs those
capabilities by including `TypeId` in the list of requirements. In the case
where no type capabilities are needed, for example the code is only manipulating
pointers to the type, you would write `T:! type` and get the efficiency of
`void*` but without giving up type safety.
```
fn SortByAddress[T:! type](v: Vector(T*)*) { ... }
```
In particular, the compiler should in general avoid monomorphizing to generate
multiple instantiations of the function in this case.
**Open question:** Should `TypeId` be
[implemented externally](terminology.md#external-impl) for types to avoid name
pollution (`.TypeName`, `.TypeHash`, etc.) unless the function specifically
requests those capabilities?
### Destructor constraints
There are four type-of-types related to
[the destructors of types](/docs/design/classes.md#destructors):
- `Concrete` types may be local or member variables.
- `Deletable` types may be safely deallocated by pointer using the `Delete`
method on the `Allocator` used to allocate it.
- `Destructible` types have a destructor and may be deallocated by pointer
using the `UnsafeDelete` method on the correct `Allocator`, but it may be
unsafe. The concerning case is deleting a pointer to a derived class through
a pointer to its base class without a virtual destructor.
- `TrivialDestructor` types have empty destructors. This type-of-type may be
used with [specialization](#lookup-resolution-and-specialization) to unlock
specific optimizations.
**Note:** The names `Deletable` and `Destructible` are
[**placeholders**](/proposals/p1154.md#type-of-type-naming) since they do not
conform to the decision on
[question-for-leads issue #1058: "How should interfaces for core functionality be named?"](https://github.com/carbon-language/carbon-lang/issues/1058).
The type-of-types `Concrete`, `Deletable`, and `TrivialDestructor` all extend
`Destructible`. Combinations of them may be formed using
[the `&` operator](#combining-interfaces-by-anding-type-of-types). For example,
a generic function that both instantiates and deletes values of a type `T` would
require `T` implement `Concrete & Deletable`.
Types are forbidden from explicitly implementing these type-of-types directly.
Instead they use
[`destructor` declarations in their class definition](/docs/design/classes.md#destructors)
and the compiler uses them to determine which of these type-of-types are
implemented.
## Generic `let`
A `let` statement inside a function body may be used to get the change in type
behavior of calling a generic function without having to introduce a function
call.
```
fn F(...) {
...
let T:! C = U;
X;
Y;
Z;
}
```
gets rewritten to:
```
fn F(...) {
...
fn Closure(T:! C where .Self == U) {
X;
Y;
Z;
}
Closure(U);
}
```
The `where .Self == U` modifier allows values to implicitly convert between type
`T`, the erased type, and type `U`, the concrete type. Note that implicit
conversion is
[only performed across a single `where` equality](#manual-type-equality). This
can be used to switch to the API of `C` when it is external, as an alternative
to [using an adapter](#use-case-accessing-external-names), or to simplify
inlining of a generic function while preserving semantics.
## Parameterized impl declarations
There are cases where an impl definition should apply to more than a single type
and interface combination. The solution is to parameterize the impl definition,
so it applies to a family of types, interfaces, or both. This includes:
- Declare an impl for a parameterized type, which may be external or declared
out-of-line.
- "Conditional conformance" where a parameterized type implements some
interface if the parameter to the type satisfies some criteria, like
implementing the same interface.
- "Blanket" impl declarations where an interface is implemented for all types
that implement another interface, or some other criteria beyond being a
specific type.
- "Wildcard" impl declarations where a family of interfaces are implemented
for single type.
### Impl for a parameterized type
Interfaces may be implemented for a parameterized type. This can be done
lexically in the class' scope:
```
class Vector(T:! type) {
impl as Iterable where .ElementType = T {
...
}
}
```
This is equivalent to naming the type between `impl` and `as`:
```
class Vector(T:! type) {
impl Vector(T) as Iterable where .ElementType = T {
...
}
}
```
An impl may be declared [external](#external-impl) by adding an `external`
keyword before `impl`. External impl declarations may also be out-of-line, but
all parameters must be declared in a `forall` clause:
```
external impl forall [T:! type] Vector(T) as Iterable
where .ElementType = T {
...
}
```
The parameter for the type can be used as an argument to the interface being
implemented:
```
class HashMap(Key:! Hashable, Value:! type) {
impl as Has(Key) { ... }
impl as Contains(HashSet(Key)) { ... }
}
```
or externally out-of-line:
```
class HashMap(Key:! Hashable, Value:! type) { ... }
external impl forall [Key:! Hashable, Value:! type]
HashMap(Key, Value) as Has(Key) { ... }
external impl forall [Key:! Hashable, Value:! type]
HashMap(Key, Value) as Contains(HashSet(Key)) { ... }
```
### Conditional conformance
[Conditional conformance](terminology.md#conditional-conformance) is expressing
that we have an `impl` of some interface for some type, but only if some
additional type restrictions are met. Examples where this would be useful
include being able to say that a container type, like `Vector`, implements some
interface when its element type satisfies the same interface:
- A container is printable if its elements are.
- A container could be compared to another container with the same element
type using a
[lexicographic comparison](https://en.wikipedia.org/wiki/Lexicographic_order)
if the element type is comparable.
- A container is copyable if its elements are.
To do this with an [out-of-line `impl`](#external-impl), specify a more-specific
`Self` type to the left of the `as` in the declaration:
```
interface Printable {
fn Print[self: Self]();
}
class Vector(T:! type) { ... }
// By saying "T:! Printable" instead of "T:! type" here,
// we constrain T to be Printable for this impl.
external impl forall [T:! Printable] Vector(T) as Printable {
fn Print[self: Self]() {
for (let a: T in self) {
// Can call `Print` on `a` since the constraint
// on `T` ensures it implements `Printable`.
a.Print();
}
}
}
```
To include these `impl` definitions inline in a `class` definition, include a
`forall` clause with a more-specific type between the `impl` and `as` keywords.
```
class Array(T:! type, template N:! i64) {
impl forall [P:! Printable] Array(P, N) as Printable { ... }
}
```
All internal `impl` declarations in the body of a `class` definition must be for
the class being defined. It is an error to declare `impl i32 as Printable`
inside `class Array`.
It is legal to add the keyword `external` before the `impl` keyword to switch to
an external impl defined lexically within the class scope. Inside the scope,
both `P` and `T` refer to the same type, but `P` has the type-of-type of
`Printable` and so has a `Print` member. The relationship between `T` and `P` is
as if there was a `where P == T` clause.
**TODO:** Need to resolve whether the `T` name can be reused, or if we require
that you need to use new names, like `P`, when creating new type variables.
**Example:** Consider a type with two parameters, like `Pair(T, U)`. In this
example, the interface `Foo(T)` is only implemented when the two types are
equal.
```
interface Foo(T:! type) { ... }
class Pair(T:! type, U:! type) { ... }
external impl forall [T:! type] Pair(T, T) as Foo(T) { ... }
```
You may also define the `impl` inline, in which case it can be internal:
```
class Pair(T:! type, U:! type) {
impl Pair(T, T) as Foo(T) { ... }
}
```
**Clarification:** Method lookup will look at all internal implementations,
whether or not the conditions on those implementations hold for the `Self` type.
If the conditions don't hold, then the call will be rejected because `Self` has
the wrong type, just like any other argument/parameter type mismatch. This means
types may not implement two different interfaces internally if they share a
member name, even if their conditions are mutually exclusive:
```
class X(T:! type) {
impl X(i32) as Foo {
fn F[self: Self]();
}
impl X(i64) as Bar {
// ❌ Illegal: name conflict between `Foo.F` and `Bar.F`
fn F[self: Self](n: i64);
}
}
```
However, the same interface may be implemented multiple times as long as there
is no overlap in the conditions:
```
class X(T:! type) {
impl X(i32) as Foo {
fn F[self: Self]();
}
impl X(i64) as Foo {
// ✅ Allowed: `X(T).F` consistently means `X(T).(Foo.F)`
fn F[self: Self]();
}
}
```
This allows a type to express that it implements an interface for a list of
types, possibly with different implementations.
In general, `X(T).F` can only mean one thing, regardless of `T`.
**Comparison with other languages:**
[Swift supports conditional conformance](https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md),
but bans cases where there could be ambiguity from overlap.
[Rust also supports conditional conformance](https://doc.rust-lang.org/rust-by-example/generics/where.html).
#### Conditional methods
A method could be defined conditionally for a type by using a more specific type
in place of `Self` in the method declaration. For example, this is how to define
a vector type that only has a `Sort` method if its elements implement the
`Comparable` interface:
```
class Vector(T:! type) {
// `Vector(T)` has a `Sort()` method if `T` impls `Comparable`.
fn Sort[C:! Comparable, addr self: Vector(C)*]();
}
```
**Comparison with other languages:** In
[Rust](https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods)
this feature is part of conditional conformance. Swift supports conditional
methods using
[conditional extensions](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553)
or
[contextual where clauses](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID628).
### Blanket impl declarations
A _blanket impl declaration_ is an `impl` declaration that could apply to more
than one root type, so the `impl` declaration will use a type variable for the
`Self` type. Here are some examples where blanket impl declarations arise:
- Any type implementing `Ordered` should get an implementation of
`PartiallyOrdered`.
```
external impl forall [T:! Ordered] T as PartiallyOrdered { ... }
```
- `T` implements `CommonType(T)` for all `T`
```
external impl forall [T:! type] T as CommonType(T)
where .Result = T { }
```
This means that every type is the common type with itself.
Blanket impl declarations must always be [external](#external-impl) and defined
lexically out-of-line.
#### Difference between a blanket impl and a named constraint
A blanket impl declaration can be used to say "any type implementing
`interface I` also implements `interface B`." Compare this with defining a
`constraint C` that requires `I`. In that case, `C` will also be implemented any
time `I` is. There are differences though:
- There can be other implementations of `interface B` without a corresponding
implementation of `I`, unless `B` has a requirement on `I`. However, the
types implementing `C` will be the same as the types implementing `I`.
- More specialized implementations of `B` can override the blanket
implementation.
### Wildcard impl declarations
A _wildcard impl declaration_ is an `impl` declaration that defines how a family
of interfaces are implemented for a single `Self` type. For example, the
`BigInt` type might implement `AddTo(T)` for all `T` that implement
`ImplicitAs(i32)`. The implementation would first convert `T` to `i32` and then
add the `i32` to the `BigInt` value.
```
class BigInt {
external impl forall [T:! ImplicitAs(i32)] as AddTo(T) { ... }
}
// Or out-of-line:
external impl forall [T:! ImplicitAs(i32)] BigInt as AddTo(T) { ... }
```
Wildcard impl declarations must always be [external](#external-impl), to avoid
having the names in the interface defined for the type multiple times.
### Combinations
The different kinds of parameters to an `impl` declarations may be combined. For
example, if `T` implements `As(U)`, then this implements `As(Optional(U))` for
`Optional(T)`:
```
external impl forall [U:! type, T:! As(U)]
Optional(T) as As(Optional(U)) { ... }
```
This has a wildcard parameter `U`, and a condition on parameter `T`.
### Lookup resolution and specialization
As much as possible, we want rules for where an impl is allowed to be defined
and for selecting which impl to use that achieve these three goals:
- Implementations have coherence, as
[defined in terminology](terminology.md#coherence). This is
[a goal for Carbon](goals.md#coherence). More detail can be found in
[this appendix with the rationale and alternatives considered](appendix-coherence.md).
- Libraries will work together as long as they pass their separate checks.
- A generic function can assume that some impl will be successfully selected
if it can see an impl that applies, even though another more specific impl
may be selected.
For this to work, we need a rule that picks a single `impl` in the case where
there are multiple `impl` definitions that match a particular type and interface
combination. This is called _specialization_ when the rule is that most specific
implementation is chosen, for some definition of specific.
#### Type structure of an impl declaration
Given an impl declaration, find the type structure by deleting deduced
parameters and replacing type parameters by a `?`. The type structure of this
declaration:
```
impl forall [T:! ..., U:! ...] Foo(T, i32) as Bar(String, U) { ... }
```
is:
```
impl Foo(?, i32) as Bar(String, ?)
```
To get a uniform representation across different `impl` definitions, before type
parameters are replaced the declarations are normalized as follows:
- For impl declarations lexically inline in a class definition, the type is
added between the `impl` and `as` keywords if the type is left out.
- Pointer types `T*` are replaced with `Ptr(T)`.
- The `external` keyword is removed, if present.
- The `forall` clause introducing type parameters is removed, if present.
- Any `where` clauses that are setting associated constants or types are
removed.
The type structure will always contain a single interface name, which is the
name of the interface being implemented, and some number of type names. Type
names can be in the `Self` type to the left of the `as` keyword, or as
parameters to other types or the interface. These names must always be defined
either in the current library or be publicly defined in some library this
library depends on.
#### Orphan rule
To achieve coherence, we need to ensure that any given impl can only be defined
in a library that must be imported for it to apply. Specifically, given a
specific type and specific interface, `impl` declarations that can match can
only be in libraries that must have been imported to name that type or
interface. This is achieved with the _orphan rule_.
**Orphan rule:** Some name from the type structure of an `impl` declaration must
be defined in the same library as the `impl`, that is some name must be _local_.
Only the implementing interface and types (self type and type parameters) in the
type structure are relevant here; an interface mentioned in a constraint is not
sufficient since it
[need not be imported](/proposals/p0920.md#orphan-rule-could-consider-interface-requirements-in-blanket-impls).
Since Carbon in addition requires there be no cyclic library dependencies, we
conclude that there is at most one library that can contain `impl` definitions
with a particular type structure.
#### Overlap rule
Given a specific concrete type, say `Foo(bool, i32)`, and an interface, say
`Bar(String, f32)`, the overlap rule picks, among all the matching `impl`
declarations, which type structure is considered "most specific" to use as the
implementation of that type for that interface.
Given two different type structures of impl declarations matching a query, for
example:
```
impl Foo(?, i32) as Bar(String, ?)
impl Foo(?, ?) as Bar(String, f32)
```
We pick the type structure with a non-`?` at the first difference as most
specific. Here we see a difference between `Foo(?, i32)` and `Foo(?, ?)`, so we
select the one with `Foo(?, i32)`, ignoring the fact that it has another `?`
later in its type structure
This rule corresponds to a depth-first traversal of the type tree to identify
the first difference, and then picking the most specific choice at that
difference.
#### Prioritization rule
Since at most one library can contain `impl` definitions with a given type
structure, all `impl` definitions with a given type structure must be in the
same library. Furthermore by the [impl declaration access rules](#access), they
will be defined in the API file for the library if they could match any query
from outside the library. If there is more than one impl with that type
structure, they must be [defined](#implementing-interfaces) or
[declared](#declaring-implementations) together in a prioritization block. Once
a type structure is selected for a query, the first impl in the prioritization
block that matches is selected.
**Open question:** How are prioritization blocks written? A block starts with a
keyword like `match_first` or `impl_priority` and then a sequence of impl
declarations inside matching curly braces `{` ... `}`.
```
match_first {
// If T is Foo prioritized ahead of T is Bar
impl forall [T:! Foo] T as Bar { ... }
impl forall [T:! Baz] T as Bar { ... }
}
```
**Open question:** How do we pick between two different prioritization blocks
when they contain a mixture of type structures? There are three options:
- Prioritization blocks implicitly define all non-empty intersections of
contained `impl` declarations, which are then selected by their type
structure.
- The compiler first picks the impl with the type pattern most favored for the
query, and then picks the definition of the highest priority matching impl
in the same prioritization block.
- All the `impl` declarations in a prioritization block are required to have
the same type structure, at a cost in expressivity.
To see the difference between the first two options, consider two libraries with
type structures as follows:
- Library B has `impl (A, ?, ?, D) as I` and `impl (?, B, ?, D) as I` in the
same prioritization block.
- Library C has `impl (A, ?, C, ?) as I`.
For the query `(A, B, C, D) as I`, using the intersection rule, library B is
considered to have the intersection impl with type structure
`impl (A, B, ?, D) as I` which is the most specific. If we instead just
considered the rules mentioned explicitly, then `impl (A, ?, C, ?) as I` from
library C is the most specific. The advantage of the implicit intersection rule
is that if library B is changed to add an impl with type structure
`impl (A, B, ?, D) as I`, it won't shift which library is serving that query.
#### Acyclic rule
A cycle is when a query, such as "does type `T` implement interface `I`?",
considers an impl that might match, and whether that impl matches is ultimately
dependent on whether that query is true. These are cycles in the graph of (type,
interface) pairs where there is an edge from pair A to pair B if whether type A
implements interface A determines whether type B implements interface B.
The test for whether something forms a cycle needs to be precise enough, and not
erase too much information when considering this graph, that these `impl`
declarations are not considered to form cycles with themselves:
```
impl forall [T:! Printable] Optional(T) as Printable;
impl forall [T:! type, U:! ComparableTo(T)] U as ComparableTo(Optional(T));
```
**Example:** If `T` implements `ComparableWith(U)`, then `U` should implement
`ComparableWith(T)`.
```
external impl forall [U:! type, T:! ComparableWith(U)]
U as ComparableWith(T);
```
This is a cycle where which types implement `ComparableWith` determines which
types implement the same interface.
**Example:** Cycles can create situations where there are multiple ways of
selecting `impl` declarations that are inconsistent with each other. Consider an
interface with two blanket `impl` declarations:
```
class Y {}
class N {}
interface True {}
impl Y as True {}
interface Z(T:! type) { let Cond:! type; }
match_first {
impl forall [T:! type, U:! Z(T) where .Cond impls True] T as Z(U)
where .Cond = N { }
impl forall [T:! type, U:! type] T as Z(U)
where .Cond = Y { }
}
```
What is `i8.(Z(i16).Cond)`? It depends on which of the two blanket impl
declarations are selected.
- An implementation of `Z(i16)` for `i8` could come from the first blanket
impl with `T == i8` and `U == i16` if `i16 impls Z(i8)` and
`i16.(Z(i8).Cond) == Y`. This condition is satisfied if `i16` implements
`Z(i8)` using the second blanket impl. In this case,
`i8.(Z(i16).Cond) == N`.
- Equally well `Z(i8)` could be implemented for `i16` using the first blanket
impl and `Z(i16)` for `i8` using the second. In this case,
`i8.(Z(i16).Cond) == Y`.
There is no reason to to prefer one of these outcomes over the other.
**Example:** Further, cycles can create contradictions in the type system:
```
class A {}
class B {}
class C {}
interface D(T:! type) { let Cond:! type; }
match_first {
impl forall [T:! type, U:! D(T) where .Cond = B] T as D(U)
where .Cond = C { }
impl forall [T:! type, U:! D(T) where .Cond = A] T as D(U)
where .Cond = B { }
impl forall [T:! type, U:! type] T as D(U)
where .Cond = A { }
}
```
What is `i8.(D(i16).Cond)`? The answer is determined by which blanket impl is
selected to implement `D(i16)` for `i8`:
- If the third blanket impl is selected, then `i8.(D(i16).Cond) == A`. This
implies that `i16.(D(i8).Cond) == B` using the second blanket impl. If that
is true, though, then our first impl choice was incorrect, since the first
blanket impl applies and is higher priority. So `i8.(D(i16).Cond) == C`. But
that means that `i16 as D(i8)` can't use the second blanket impl.
- For the second blanket impl to be selected, so `i8.(D(i16).Cond) == B`,
`i16.(D(i8).Cond)` would have to be `A`. This happens when `i16` implements
`D(i8)` using the third blanket impl. However, `i8.(D(i16).Cond) == B` means
that there is a higher priority implementation of `D(i8).Cond` for `i16`.
In either case, we arrive at a contradiction.
The workaround for this problem is to either split an interface in the cycle in
two, with a blanket implementation of one from the other, or move some of the
criteria into a [named constraint](#named-constraints).
**Concern:** Cycles could be spread out across libraries with no dependencies
between them. This means there can be problems created by a library that are
only detected by its users.
**Open question:** Should Carbon reject cycles in the absence of a query? The
two options here are:
- Combining `impl` declarations gives you an immediate error if there exists
queries using them that have cycles.
- Only when a query reveals a cyclic dependency is an error reported.
**Open question:** In the second case, should we ignore cycles if they don't
affect the result of the query? For example, the cycle might be among
implementations that are lower priority.
#### Termination rule
It is possible to have a set of `impl` declarations where there isn't a cycle,
but the graph is infinite. Without some rule to prevent exhaustive exploration
of the graph, determining whether a type implements an interface could run
forever.
**Example:** It could be that `A` implements `B`, so `A impls B` if
`Optional(A) impls B`, if `Optional(Optional(A)) impls B`, and so on. This could
be the result of a single impl:
```
impl forall [A:! type where Optional(.Self) impls B] A as B { ... }
```
This problem can also result from a chain of `impl` declarations, as in
`A impls B` if `A* impls C`, if `Optional(A) impls B`, and so on.
Rust solves this problem by imposing a recursion limit, much like C++ compilers
use to terminate template recursion. This goes against
[Carbon's goal of predictability in generics](goals.md#predictability), but at
this time there are no known alternatives. Unfortunately, the approach Carbon
uses to avoid undecidability for type equality,
[providing an explicit proof in the source](#manual-type-equality), can't be
used here. The code triggering the query asking whether some type implements an
interface will typically be generic code with know specific knowledge about the
types involved, and won't be in a position to provide a manual proof that the
implementation should exist.
**Open question:** Is there some restriction on `impl` declarations that would
allow our desired use cases, but allow the compiler to detect non-terminating
cases? Perhaps there is some sort of complexity measure Carbon can require
doesn't increase when recursing?
### `final` impl declarations
There are cases where knowing that a parameterized impl won't be specialized is
particularly valuable. This could let the compiler know the return type of a
generic function call, such as using an operator:
```
// Interface defining the behavior of the prefix-* operator
interface Deref {
let Result:! type;
fn DoDeref[self: Self]() -> Result;
}
// Types implementing `Deref`
class Ptr(T:! type) {
...
external impl as Deref where .Result = T {
fn DoDeref[self: Self]() -> Result { ... }
}
}
class Optional(T:! type) {
...
external impl as Deref where .Result = T {
fn DoDeref[self: Self]() -> Result { ... }
}
}
fn F[T:! type](x: T) {
// uses Ptr(T) and Optional(T) in implementation
}
```
The concern is the possibility of specializing `Optional(T) as Deref` or
`Ptr(T) as Deref` for a more specific `T` means that the compiler can't assume
anything about the return type of `Deref.DoDeref` calls. This means `F` would in
practice have to add a constraint, which is both verbose and exposes what should
be implementation details:
```
fn F[T:! type where Optional(T).(Deref.Result) == .Self
and Ptr(T).(Deref.Result) == .Self](x: T) {
// uses Ptr(T) and Optional(T) in implementation
}
```
To mark an impl as not able to be specialized, prefix it with the keyword
`final`:
```
class Ptr(T:! type) {
...
// Note: added `final`
final external impl as Deref where .Result = T {
fn DoDeref[self: Self]() -> Result { ... }
}
}
class Optional(T:! type) {
...
// Note: added `final`
final external impl as Deref where .Result = T {
fn DoDeref[self: Self]() -> Result { ... }
}
}
// ❌ Illegal: external impl Ptr(i32) as Deref { ... }
// ❌ Illegal: external impl Optional(i32) as Deref { ... }
```
This prevents any higher-priority impl that overlaps a final impl from being
defined. Further, if the Carbon compiler sees a matching final impl, it can
assume it won't be specialized so it can use the assignments of the associated
types in that impl definition.
```
fn F[T:! type](x: T) {
var p: Ptr(T) = ...;
// *p has type `T`
var o: Optional(T) = ...;
// *o has type `T`
}
```
#### Libraries that can contain a `final` impl
To prevent the possibility of two unrelated libraries defining conflicting impl
declarations, Carbon restricts which libraries may declare an impl as `final` to
only:
- the library declaring the impl's interface and
- the library declaring the root of the `Self` type.
This means:
- A blanket impl with type structure `impl ? as MyInterface(...)` may only be
defined in the same library as `MyInterface`.
- An impl with type structure `impl MyType(...) as MyInterface(...)` may be
defined in the library with `MyType` or `MyInterface`.
These restrictions ensure that the Carbon compiler can locally check that no
higher-priority impl is defined superseding a `final` impl.
- An impl with type structure `impl MyType(...) as MyInterface(...)` defined
in the library with `MyType` must import the library defining `MyInterface`,
and so will be able to see any final blanket impl declarations.
- A blanket impl with type structure
`impl ? as MyInterface(...ParameterType(...)...)` may be defined in the
library with `ParameterType`, but that library must import the library
defining `MyInterface`, and so will be able to see any `final` blanket impl
declarations that might overlap. A final impl with type structure
`impl MyType(...) as MyInterface(...)` would be given priority over any
overlapping blanket impl defined in the `ParameterType` library.
- An impl with type structure
`impl MyType(...ParameterType(...)...) as MyInterface(...)` may be defined
in the library with `ParameterType`, but that library must import the
libraries defining `MyType` and `MyInterface`, and so will be able to see
any `final` `impl` declarations that might overlap.
### Comparison to Rust
Rust has been designing a specialization feature, but it has not been completed.
Luckily, Rust team members have done a lot of blogging during their design
process, so Carbon can benefit from the work they have done. However, getting
specialization to work for Rust is complicated by the need to maintain
compatibility with existing Rust code. This motivates a number of Rust rules
where Carbon can be simpler. As a result there are both similarities and
differences between the Carbon and Rust plans:
- A Rust impl defaults to not being able to be specialized, with a `default`
keyword used to opt-in to allowing specialization, reflecting the existing
code base developed without specialization. Carbon `impl` declarations
default to allowing specialization, with restrictions on which may be
declared `final`.
- Since a Rust impl is not specializable by default, generic functions can
assume that if a matching blanket impl declaration is found, the associated
types from that impl will be used. In Carbon, if a generic function requires
an associated type to have a particular value, the function commonly will
need to state that using an explicit constraint.
- Carbon will not have the "fundamental" attribute used by Rust on types or
traits, as described in
[Rust RFC 1023: "Rebalancing Coherence"](https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html).
- Carbon will not use "covering" rules, as described in
[Rust RFC 2451: "Re-Rebalancing Coherence"](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html)
and
[Little Orphan Impls: The covered rule](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/#the-covered-rule).
- Like Rust, Carbon does use ordering, favoring the `Self` type and then the
parameters to the interface in left-to-right order, see
[Rust RFC 1023: "Rebalancing Coherence"](https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html)
and
[Little Orphan Impls: The ordered rule](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/#the-ordered-rule),
but the specifics are different.
- Carbon is not planning to support any inheritance of implementation between
impl definitions. This is more important to Rust since Rust does not support
class inheritance for implementation reuse. Rust has considered multiple
approaches here, see
[Aaron Turon: "Specialize to Reuse"](http://aturon.github.io/tech/2015/09/18/reuse/)
and
[Supporting blanket impls in specialization](http://smallcultfollowing.com/babysteps/blog/2016/10/24/supporting-blanket-impls-in-specialization/).
- [Supporting blanket impls in specialization](http://smallcultfollowing.com/babysteps/blog/2016/10/24/supporting-blanket-impls-in-specialization/)
proposes a specialization rule for Rust that considers type structure before
other constraints, as in Carbon, though the details differ.
- Rust has more orphan restrictions to avoid there being cases where it is
ambiguous which impl should be selected. Carbon instead has picked a total
ordering on type structures, picking one as higher priority even without one
being more specific in the sense of only applying to a subset of types.
## Forward declarations and cyclic references
Interfaces, named constraints, and their implementations may be forward declared
and then later defined. This is needed to allow cyclic references, for example
when declaring the edges and nodes of a graph. It is also a tool that may be
used to make code more readable.
The [interface](#interfaces), [named constraint](#named-constraints), and
[implementation](#implementing-interfaces) sections describe the syntax for
their _definition_, which consists of a declaration followed by a body contained
in curly braces `{` ... `}`. A _forward declaration_ is a declaration followed
by a semicolon `;`. A forward declaration is a promise that the entity being
declared will be defined later. Between the first declaration of an entity,
which may be in a forward declaration or the first part of a definition, and the
end of the definition the interface or implementation is called _incomplete_.
There are additional restrictions on how the name of an incomplete entity may be
used.
### Declaring interfaces and named constraints
The declaration for an interface or named constraint consists of:
- an optional access-control keyword like `private`,
- the keyword introducer `interface`, `constraint`, or `template constraint`,
- the name of the interface or constraint, and
- the parameter list, if any.
The name of an interface or constraint can not be used until its first
declaration is complete. In particular, it is illegal to use the name of the
interface in its parameter list. There is a
[workaround](#interfaces-with-parameters-constrained-by-the-same-interface) for
the use cases when this would come up.
An expression forming a constraint, such as `C & D`, is incomplete if any of the
interfaces or constraints used in the expression are incomplete. A constraint
expression using a [`where` clause](#where-constraints), like `C where ...`, is
invalid if `C` is incomplete, since there is no way to look up member names of
`C` that appear after `where`.
An interface or named constraint may be forward declared subject to these rules:
- The definition must be in the same file as the declaration.
- Only the first declaration may have an access-control keyword.
- An incomplete interface or named constraint may be used as constraints in
declarations of types, functions, interfaces, or named constraints. This
includes an `impl as` or `extends` declaration inside an interface or named
constraint, but excludes specifying the values for associated constants
because that would involve name lookup into the incomplete constraint.
- An attempt to define the body of a generic function using an incomplete
interface or named constraint is illegal.
- An attempt to call a generic function using an incomplete interface or named
constraint in its signature is illegal.
- Any name lookup into an incomplete interface or named constraint is an
error. For example, it is illegal to attempt to access a member of an
interface using `MyInterface.MemberName` or constrain a member using a
`where` clause.
If `C` is the name of an incomplete interface or named constraint, then it can
be used in the following contexts:
- ✅ `T:! C`
- ✅ `C & D`
- There may be conflicts between `C` and `D` making this invalid that will
only be discovered once they are both complete.
- ✅ `interface `...` { impl` ... `as C; }` or `constraint `...` { impl` ...
`as C; }`
- Nothing implied by implementing `C` will be visible until `C` is
complete.
- ✅ `T:! C` ... `T impls C`
- ✅ `T:! A & C` ... `T impls C`
- This includes constructs requiring `T impls C` such as `T as C` or
`U:! C = T`.
- ✅ `external impl `...` as C;`
- Checking that all associated constants of `C` are correctly assigned
values will be delayed until `C` is complete.
An incomplete `C` cannot be used in the following contexts:
- ❌ `T:! C` ... `T.X`
- ❌ `T:! C where `...
- ❌ `class `...` { impl as C; }`
- The names of `C` are added to the class, and so those names need to be
known.
- ❌ `T:! C` ... `T impls A` where `A` is an interface or named constraint
different from `C`
- Need to see the definition of `C` to see if it implies `A`.
- ❌ `external impl` ... `as C {` ... `}`
**Future work:** It is currently undecided whether an interface needs to be
complete to be extended, as in:
```
interface I { extends C; }
```
There are three different approaches being considered:
- If we detect name collisions between the members of the interface `I` and
`C` when the interface `I` is defined, then we need `C` to be complete.
- If we instead only generate errors on ambiguous use of members with the same
name, as we do with `A & B`, then we don't need to require `C` to be
complete.
- Another option, being discussed in
[#2355](https://github.com/carbon-language/carbon-lang/issues/2355), is that
names in interface `I` shadow the names in any interface being extended,
then `C` would not be required to be complete.
### Declaring implementations
The declaration of an interface implementation consists of:
- optional modifier keywords `final`, `external`,
- the keyword introducer `impl`,
- an optional deduced parameter list in square brackets `[`...`]`,
- a type, including an optional parameter pattern,
- the keyword `as`, and
- a [type-of-type](#type-of-types), including an optional
[parameter pattern](#parameterized-interfaces) and
[`where` clause](#where-constraints) assigning
[associated constants](#associated-constants) and
[associated types](#associated-types).
An implementation of an interface for a type may be forward declared subject to
these rules:
- The definition must be in the same library as the declaration. They must
either be in the same file, or the declaration can be in the API file and
the definition in an impl file. **Future work:** Carbon may require
[parameterized impl definitions](#parameterized-impl-declarations) to be in
the API file, to support separate compilation.
- If there is both a forward declaration and a definition, only the first
declaration must specify the assignment of associated constants with a
`where` clause. Later declarations may omit the `where` clause by writing
`where _` instead.
- You may forward declare an implementation of a defined interface but not an
incomplete interface. This allows the assignment of associated constants in
the `impl` declaration to be verified. An impl forward declaration may be
for any declared type, whether it is incomplete or defined. Note that this
does not apply to `impl as` declarations in an interface or named constraint
definition, as those are considered interface requirements not forward
declarations.
- Every internal implementation must be declared (or defined) inside the scope
of the class definition. It may also be declared before the class definition
or defined afterwards. Note that the class itself is incomplete in the scope
of the class definition, but member function bodies defined inline are
processed
[as if they appeared immediately after the end of the outermost enclosing class](/docs/project/principles/information_accumulation.md#exceptions).
- For [coherence](goals.md#coherence), we require that any impl that matches
an [impl lookup](#impl-lookup) query in the same file, must be declared
before the query. This can be done with a definition or a forward
declaration.
### Matching and agreeing
Carbon needs to determine if two declarations match in order to say which
definition a forward declaration corresponds to and to verify that nothing is
defined twice. Declarations that match must also agree, meaning they are
consistent with each other.
Interface and named constraint declarations match if their names are the same
after name and alias resolution. To agree:
- The introducer keyword or keywords much be the same.
- The types and order of parameters in the parameter list, if any, must match.
The parameter names may be omitted, but if they are included in both
declarations, they must match.
- Types agree if they correspond to the same expression tree, after name and
alias resolution and canonicalization of parentheses. Note that no other
evaluation of type expressions is performed.
Interface implementation declarations match if the type and interface
expressions match:
- If the type part is omitted, it is rewritten to `Self` in the context of the
declaration.
- `Self` is rewritten to its meaning in the scope it is used. In a class
scope, this should match the type name and optional parameter expression
after `class`. So in `class MyClass extends MyBase { ... }`, `Self` is
rewritten to `MyClass`. In `class Vector(T:! Movable) { ... }`, `Self` is
rewritten to `Vector(T:! Movable)`.
- Types match if they have the same name after name and alias resolution and
the same parameters, or are the same type parameter.
- Interfaces match if they have the same name after name and alias resolution
and the same parameters. Note that a named constraint that is equivalent to
an interface, as in `constraint Equivalent { extends MyInterface; }`, is not
considered to match.
For implementations to agree:
- The presence of modifier keywords such as `external` before `impl` must
match between a forward declaration and definition.
- If either declaration includes a `where` clause, they must both include one.
If neither uses `where _`, they must match in that they produce the
associated constants with the same values considered separately.
### Declaration examples
```
// Forward declaration of interfaces
interface Interface1;
interface Interface2;
interface Interface3;
interface Interface4;
interface Interface5;
interface Interface6;
// Forward declaration of class type
class MyClass;
// ❌ Illegal: Can't declare implementation of incomplete
// interface.
// external impl MyClass as Interface1;
// Definition of interfaces that were previously declared
interface Interface1 {
let T1:! type;
}
interface Interface2 {
let T2:! type;
}
interface Interface3 {
let T3:! type;
}
interface Interface4 {
let T4:! type;
}
// Forward declaration of external implementations
external impl MyClass as Interface1 where .T1 = i32;
external impl MyClass as Interface2 where .T2 = bool;
// Forward declaration of an internal implementation
impl MyClass as Interface3 where .T3 = f32;
impl MyClass as Interface4 where .T4 = String;
interface Interface5 {
let T5:! type;
}
interface Interface6 {
let T6:! type;
}
// Definition of the previously declared class type
class MyClass {
// Definition of previously declared external impl.
// Note: no need to repeat assignments to associated
// constants.
external impl as Interface1 where _ { }
// Definition of previously declared internal impl.
// Note: allowed even though `MyClass` is incomplete.
// Note: allowed but not required to repeat `where`
// clause.
impl as Interface3 where .T3 = f32 { }
// Redeclaration of previously declared internal impl.
// Every internal implementation must be declared in
// the class definition.
impl as Interface4 where _;
// Forward declaration of external implementation.
external impl MyClass as Interface5 where .T5 = u64;
// Forward declaration of internal implementation.
impl MyClass as Interface6 where .T6 = u8;
}
// It would be legal to move the following definitions
// from the API file to the implementation file for
// this library.
// Definition of implementations previously declared
// external.
external impl MyClass as Interface2 where _ { }
external impl MyClass as Interface5 where _ { }
// Definition of implementations previously declared
// internal.
impl MyClass as Interface4 where _ { }
impl MyClass as Interface6 where _ { }
```
### Example of declaring interfaces with cyclic references
In this example, `Node` has an `EdgeType` associated type that is constrained to
implement `Edge`, and `Edge` has a `NodeType` associated type that is
constrained to implement `Node`. Furthermore, the `NodeType` of an `EdgeType` is
the original type, and the other way around. This is accomplished by naming and
then forward declaring the constraints that can't be stated directly:
```
// Forward declare interfaces used in
// parameter lists of constraints.
interface Edge;
interface Node;
// Forward declare named constraints used in
// interface definitions.
private constraint EdgeFor(N:! Node);
private constraint NodeFor(E:! Edge);
// Define interfaces using named constraints.
interface Edge {
let NodeType:! NodeFor(Self);
fn Head[self: Self]() -> NodeType;
}
interface Node {
let EdgeType:! EdgeFor(Self);
fn Edges[self: Self]() -> Vector(EdgeType);
}
// Now that the interfaces are defined, can
// refer to members of the interface, so it is
// now legal to define the named constraints.
constraint EdgeFor(N:! Node) {
extends Edge where .NodeType == N;
}
constraint NodeFor(E:! Edge) {
extends Node where .EdgeType == E;
}
```
### Interfaces with parameters constrained by the same interface
To work around
[the restriction about not being able to name an interface in its parameter list](#declaring-interfaces-and-named-constraints),
instead include that requirement in the body of the interface.
```
// Want to require that `T` satisfies `CommonType(Self)`,
// but that can't be done in the parameter list.
interface CommonType(T:! type) {
let Result:! type;
// Instead add the requirement inside the definition.
impl T as CommonType(Self);
}
```
Note however that `CommonType` is still incomplete inside its definition, so no
constraints on members of `CommonType` are allowed, and that this `impl T as`
declaration
[must involve `Self`](#interface-requiring-other-interfaces-revisited).
```
interface CommonType(T:! type) {
let Result:! type;
// ❌ Illegal: `CommonType` is incomplete
impl T as CommonType(Self) where .Result == Result;
}
```
Instead, a forward-declared named constraint can be used in place of the
constraint that can only be defined later. This is
[the same strategy used to work around cyclic references](#example-of-declaring-interfaces-with-cyclic-references).
```
private constraint CommonTypeResult(T:! type, R:! type);
interface CommonType(T:! type) {
let Result:! type;
// ✅ Allowed: `CommonTypeResult` is incomplete, but
// no members are accessed.
impl T as CommonTypeResult(Self, Result);
}
constraint CommonTypeResult(T:! type, R:! type) {
extends CommonType(T) where .Result == R;
}
```
## Interface members with definitions
Interfaces may provide definitions for members, such as a function body for an
associated function or method or a value for an associated constant. If these
definitions may be overridden in implementations, they are called "defaults" and
prefixed with the `default` keyword. Otherwise they are called "final members"
and prefixed with the `final` keyword.
### Interface defaults
An interface may provide a default implementation of methods in terms of other
methods in the interface.
```
interface Vector {
fn Add[self: Self](b: Self) -> Self;
fn Scale[self: Self](v: f64) -> Self;
// Default definition of `Invert` calls `Scale`.
default fn Invert[self: Self]() -> Self {
return self.Scale(-1.0);
}
}
```
A default function or method may also be defined out of line, later in the same
file as the interface definition:
```
interface Vector {
fn Add[self: Self](b: Self) -> Self;
fn Scale[self: Self](v: f64) -> Self;
default fn Invert[self: Self]() -> Self;
}
// `Vector` is considered complete at this point,
// even though `Vector.Invert` is still incomplete.
fn Vector.Invert[self: Self]() -> Self {
return self.Scale(-1.0);
}
```
An impl of that interface for a type may omit a definition of `Invert` to use
the default, or provide a definition to override the default.
Interface defaults are helpful for [evolution](#evolution), as well as reducing
boilerplate. Defaults address the gap between the minimum necessary for a type
to provide the desired functionality of an interface and the breadth of API that
developers desire. As an example, in Rust the
[iterator trait](https://doc.rust-lang.org/std/iter/trait.Iterator.html) only
has one required method but dozens of "provided methods" with defaults.
Defaults may also be provided for associated constants, such as associated
types, and interface parameters, using the `= <default value>` syntax.
```
interface Add(Right:! type = Self) {
default let Result:! type = Self;
fn DoAdd[self: Self](right: Right) -> Result;
}
impl String as Add() {
// Right == Result == Self == String
fn DoAdd[self: Self](right: Self) -> Self;
}
```
Note that `Self` is a legal default value for an associated type or type
parameter. In this case the value of those names is not determined until `Self`
is, so `Add()` is equivalent to the constraint:
```
// Equivalent to Add()
constraint AddDefault {
extends Add(Self);
}
```
Note also that the parenthesis are required after `Add`, even when all
parameters are left as their default values.
More generally, default expressions may reference other associated types or
`Self` as parameters to type constructors. For example:
```
interface Iterator {
let Element:! type;
default let Pointer:! type = Element*;
}
```
Carbon does **not** support providing a default implementation of a required
interface.
```
interface TotalOrder {
fn TotalLess[self: Self](right: Self) -> bool;
// ❌ Illegal: May not provide definition
// for required interface.
impl as PartialOrder {
fn PartialLess[self: Self](right: Self) -> bool {
return self.TotalLess(right);
}
}
}
```
The workaround for this restriction is to use a
[blanket impl declaration](#blanket-impl-declarations) instead:
```
interface TotalOrder {
fn TotalLess[self: Self](right: Self) -> bool;
impl as PartialOrder;
}
external impl forall [T:! TotalOrder] T as PartialOrder {
fn PartialLess[self: Self](right: Self) -> bool {
return self.TotalLess(right);
}
}
```
Note that by the [orphan rule](#orphan-rule), this blanket impl must be defined
in the same library as `PartialOrder`.
**Comparison with other languages:** Rust supports specifying defaults for
[methods](https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations),
[interface parameters](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#default-generic-type-parameters-and-operator-overloading),
and
[associated constants](https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants-examples).
Rust has found them valuable.
### `final` members
As an alternative to providing a definition of an interface member as a default,
members marked with the `final` keyword will not allow that definition to be
overridden in `impl` definitions.
```
interface TotalOrder {
fn TotalLess[self: Self](right: Self) -> bool;
final fn TotalGreater[self: Self](right: Self) -> bool {
return right.TotalLess(self);
}
}
class String {
impl as TotalOrder {
fn TotalLess[self: Self](right: Self) -> bool { ... }
// ❌ Illegal: May not provide definition of final
// method `TotalGreater`.
fn TotalGreater[self: Self](right: Self) -> bool { ... }
}
}
interface Add(T:! type = Self) {
// `AddWith` *always* equals `T`
final let AddWith:! type = T;
// Has a *default* of `Self`
let Result:! type = Self;
fn DoAdd[self: Self](right: AddWith) -> Result;
}
```
Final members may also be defined out-of-line:
```
interface TotalOrder {
fn TotalLess[self: Self](right: Self) -> bool;
final fn TotalGreater[self: Self](right: Self) -> bool;
}
// `TotalOrder` is considered complete at this point, even
// though `TotalOrder.TotalGreater` is not yet defined.
fn TotalOrder.TotalGreater[self: Self](right: Self) -> bool {
return right.TotalLess(self);
}
```
There are a few reasons for this feature:
- When overriding would be inappropriate.
- Matching the functionality of non-virtual methods in base classes, so
interfaces can be a replacement for inheritance.
- Potentially reduce dynamic dispatch when using the interface in a
[`DynPtr`](#dynamic-types).
Note that this applies to associated entities, not interface parameters.
## Interface requiring other interfaces revisited
Recall that an
[interface can require another interface be implemented for the type](#interface-requiring-other-interfaces),
as in:
```
interface Iterable {
impl as Equatable;
// ...
}
```
This states that the type implementing the interface `Iterable`, which in this
context is called `Self`, must also implement the interface `Equatable`. As is
done with [conditional conformance](#conditional-conformance), we allow another
type to be specified between `impl` and `as` to say some type other than `Self`
must implement an interface. For example,
```
interface IntLike {
impl i32 as As(Self);
// ...
}
```
says that if `Self` implements `IntLike`, then `i32` must implement `As(Self)`.
Similarly,
```
interface CommonTypeWith(T:! type) {
impl T as CommonTypeWith(Self);
// ...
}
```
says that if `Self` implements `CommonTypeWith(T)`, then `T` must implement
`CommonTypeWith(Self)`.
The previous description of `impl as` in an interface definition matches the
behavior of using a default of `Self` when the type between `impl` and `as` is
omitted. So the previous definition of `interface Iterable` is equivalent to:
```
interface Iterable {
// ...
impl Self as Equatable;
// Equivalent to: impl as Equatable;
}
```
An `impl`...`as` constraint in an `interface`, or `constraint`, definition must
still use `Self` in some way. It can be the implicit `Self` when nothing is
specified between `impl` and `as`, or it can be an argument to either the type
or interface. For example:
- ✅ Allowed: `impl as Equatable`
- ✅ Allowed: `impl Self as Equatable`
- ✅ Allowed: `impl Vector(Self) as Equatable`
- ✅ Allowed: `impl i32 as CommonTypeWith(Self)`
- ✅ Allowed: `impl Self as CommonTypeWith(Self)`
- ❌ Error: `impl i32 as Equatable`
- ❌ Error: `impl T as Equatable` where `T` is some parameter to the interface
This restriction allows the Carbon compiler to know where to look for facts
about a type. If `impl i32 as Equatable` could appear in any `interface`
definition, that implies having to search all of them when considering what
interfaces `i32` implements. This creates a coherence problem, since then the
set of facts true for a type would depend on which interfaces have been
imported.
When implementing an interface with an `impl as` requirement, that requirement
must be satisfied by an implementation in an imported library, an implementation
somewhere in the same file, or a constraint in the impl declaration.
Implementing the requiring interface is a promise that the requirement will be
implemented. This is like a
[forward declaration of an impl](#declaring-implementations) except that the
definition can be broader instead of being required to match exactly.
```
// `Iterable` requires `Equatable`, so there must be some
// impl of `Equatable` for `Vector(i32)` in this file.
external impl Vector(i32) as Iterable { ... }
fn RequiresEquatable[T:! Equatable](x: T) { ... }
fn ProcessVector(v: Vector(i32)) {
// ✅ Allowed since `Vector(i32)` is known to
// implement `Equatable`.
RequiresEquatable(v);
}
// Satisfies the requirement that `Vector(i32)` must
// implement `Equatable` since `i32` impls `Equatable`.
external impl forall [T:! Equatable] Vector(T) as Equatable { ... }
```
In some cases, the interface's requirement can be trivially satisfied by the
implementation itself, as in:
```
impl forall [T:! type] T as CommonTypeWith(T) { ... }
```
Here is an example where the requirement of interface `Iterable` that the type
implements interface `Equatable` is satisfied by a constraint in the `impl`
declaration:
```
class Foo(T:! type) {}
// This is allowed because we know that an `impl Foo(T) as Equatable`
// will exist for all types `T` for which this impl is used, even
// though there's neither an imported impl nor an impl in this file.
external impl forall [T:! type where Foo(T) impls Equatable]
Foo(T) as Iterable {}
```
This might be used to provide an implementation of `Equatable` for types that
already satisfy the requirement of implementing `Iterable`:
```
class Bar {}
external impl Foo(Bar) as Equatable {}
// Gives `Foo(Bar) impls Iterable` using the blanket impl of
// `Iterable` for `Foo(T)`.
```
### Requirements with `where` constraints
An interface implementation requirement with a `where` clause is harder to
satisfy. Consider an interface `B` that has a requirement that interface `A` is
also implemented.
```
interface A(T:! type) {
let Result:! type;
}
interface B(T:! type) {
impl as A(T) where .Result == i32;
}
```
An implementation of `B` for a set of types can only be valid if there is a
visible implementation of `A` with the same `T` parameter for those types with
the `.Result` associated type set to `i32`. That is
[not sufficient](/proposals/p1088.md#less-strict-about-requirements-with-where-clauses),
though, unless the implementation of `A` can't be specialized, either because it
is [marked `final`](#final-impl-declarations) or is not
[parameterized](#parameterized-impl-declarations). Implementations in other
libraries can't make `A` be implemented for fewer types, but can cause `.Result`
to have a different assignment.
## Observing a type implements an interface
An [`observe` declaration](#observe-declarations) can be used to show that two
types are equal so code can pass type checking without explicitly writing casts,
without requiring the compiler to do a unbounded search that may not terminate.
An `observe` declaration can also be used to show that a type implements an
interface, in cases where the compiler will not work this out for itself.
### Observing interface requirements
One situation where this occurs is when there is a chain of
[interfaces requiring other interfaces](#interface-requiring-other-interfaces-revisited).
During the `impl` validation done during type checking, Carbon will only
consider the interfaces that are direct requirements of the interfaces the type
is known to implement. An `observe...impls` declaration can be used to add an
interface that is a direct requirement to the set of interfaces whose direct
requirements will be considered for that type. This allows a developer to
provide a proof that there is a sequence of requirements that demonstrate that a
type implements an interface, as in this example:
```
interface A { }
interface B { impl as A; }
interface C { impl as B; }
interface D { impl as C; }
fn RequiresA[T:! A](x: T);
fn RequiresC[T:! C](x: T);
fn RequiresD[T:! D](x: T) {
// ✅ Allowed: `D` directly requires `C` to be implemented.
RequiresC(x);
// ❌ Illegal: No direct connection between `D` and `A`.
// RequiresA(x);
// `T` impls `D` and `D` directly requires `C` to be
// implemented.
observe T impls C;
// `T` impls `C` and `C` directly requires `B` to be
// implemented.
observe T impls B;
// ✅ Allowed: `T` impls `B` and `B` directly requires
// `A` to be implemented.
RequiresA(x);
}
```
Note that `observe` statements do not affect which impl is selected during code
generation. For coherence, the impl used for a (type, interface) pair must
always be the same, independent of context. The
[termination rule](#termination-rule) governs when compilation may fail when the
compiler can't determine the impl to select.
### Observing blanket impl declarations
An `observe...impls` declaration can also be used to observe that a type
implements an interface because there is a
[blanket impl declaration](#blanket-impl-declarations) in terms of requirements
a type is already known to satisfy. Without an `observe` declaration, Carbon
will only use blanket impl declarations that are directly satisfied.
```
interface A { }
interface B { }
interface C { }
interface D { }
impl forall [T:! A] T as B { }
impl forall [T:! B] T as C { }
impl forall [T:! C] T as D { }
fn RequiresD(T:! D)(x: T);
fn RequiresB(T:! B)(x: T);
fn RequiresA(T:! A)(x: T) {
// ✅ Allowed: There is a blanket implementation
// of `B` for types implementing `A`.
RequiresB(x);
// ❌ Illegal: No implementation of `D` for type
// `T` implementing `A`
// RequiresD(x);
// There is a blanket implementation of `B` for
// types implementing `A`.
observe T impls B;
// There is a blanket implementation of `C` for
// types implementing `B`.
observe T impls C;
// ✅ Allowed: There is a blanket implementation
// of `D` for types implementing `C`.
RequiresD(x);
}
```
In the case of an error, a quality Carbon implementation will do a deeper search
for chains of requirements and blanket impl declarations and suggest `observe`
declarations that would make the code compile if any solution is found.
## Operator overloading
Operations are overloaded for a type by implementing an interface specific to
that interface for that type. For example, types implement the `Negatable`
interface to overload the unary `-` operator:
```
// Unary `-`.
interface Negatable {
let Result:! type = Self;
fn Negate[self: Self]() -> Result;
}
```
Expressions using operators are rewritten into calls to these interface methods.
For example, `-x` would be rewritten to `x.(Negatable.Negate)()`.
The interfaces and rewrites used for a given operator may be found in the
[expressions design](/docs/design/expressions/README.md).
[Question-for-leads issue #1058](https://github.com/carbon-language/carbon-lang/issues/1058)
defines the naming scheme for these interfaces.
### Binary operators
Binary operators will have an interface that is
[parameterized](#parameterized-interfaces) based on the second operand. For
example, to say a type may be converted to another type using an `as`
expression, implement the
[`As` interface](/docs/design/expressions/as_expressions.md#extensibility):
```
interface As(Dest:! type) {
fn Convert[self: Self]() -> Dest;
}
```
The expression `x as U` is rewritten to `x.(As(U).Convert)()`. Note that the
parameterization of the interface means it can be implemented multiple times to
support multiple operand types.
Unlike `as`, for most binary operators the interface's argument will be the
_type_ of the right-hand operand instead of its _value_. Consider an interface
for a binary operator like `*`:
```
// Binary `*`.
interface MultipliableWith(U:! type) {
let Result:! type = Self;
fn Multiply[self: Self](other: U) -> Result;
}
```
A use of binary `*` in source code will be rewritten to use this interface:
```
var left: Meters = ...;
var right: f64 = ...;
var result: auto = left * right;
// Equivalent to:
var equivalent: left.(MultipliableWith(f64).Result)
= left.(MultipliableWith(f64).Multiply)(right);
```
Note that if the types of the two operands are different, then swapping the
order of the operands will result in a different implementation being selected.
It is up to the developer to make those consistent when that is appropriate. The
standard library will provide [adapters](#adapting-types) for defining the
second implementation from the first, as in:
```
interface ComparableWith(RHS:! type) {
fn Compare[self: Self](right: RHS) -> CompareResult;
}
adapter ReverseComparison
(T:! type, U:! ComparableWith(RHS)) for T {
impl as ComparableWith(U) {
fn Compare[self: Self](right: RHS) -> CompareResult {
return ReverseCompareResult(right.Compare(self));
}
}
}
external impl SongByTitle as ComparableWith(SongTitle);
external impl SongTitle as ComparableWith(SongByTitle)
= ReverseComparison(SongTitle, SongByTitle);
```
In some cases the reverse operation may not be defined. For example, a library
might support subtracting a vector from a point, but not the other way around.
Further note that even if the reverse implementation exists,
[the impl prioritization rule](#prioritization-rule) might not pick it. For
example, if we have two types that support comparison with anything implementing
an interface that the other implements:
```
interface IntLike {
fn AsInt[self: Self]() -> i64;
}
class EvenInt { ... }
external impl EvenInt as IntLike;
external impl EvenInt as ComparableWith(EvenInt);
// Allow `EvenInt` to be compared with anything that
// implements `IntLike`, in either order.
external impl forall [T:! IntLike] EvenInt as ComparableWith(T);
external impl forall [T:! IntLike] T as ComparableWith(EvenInt);
class PositiveInt { ... }
external impl PositiveInt as IntLike;
external impl PositiveInt as ComparableWith(PositiveInt);
// Allow `PositiveInt` to be compared with anything that
// implements `IntLike`, in either order.
external impl forall [T:! IntLike] PositiveInt as ComparableWith(T);
external impl forall [T:! IntLike] T as ComparableWith(PositiveInt);
```
Then it will favor selecting the implementation based on the type of the
left-hand operand:
```
var even: EvenInt = ...;
var positive: PositiveInt = ...;
// Uses `EvenInt as ComparableWith(T)` impl
if (even < positive) { ... }
// Uses `PositiveInt as ComparableWith(T)` impl
if (positive > even) { ... }
```
### `like` operator for implicit conversions
Because the type of the operands is directly used to select the implementation
to use, there are no automatic implicit conversions, unlike with function or
method calls. Given both a method and an interface implementation for
multiplying by a value of type `f64`:
```
class Meters {
fn Scale[self: Self](s: f64) -> Self;
}
// "Implementation One"
external impl Meters as MultipliableWith(f64)
where .Result = Meters {
fn Multiply[self: Self](other: f64) -> Result {
return self.Scale(other);
}
}
```
the method will work with any argument that can be implicitly converted to `f64`
but the operator overload will only work with values that have the specific type
of `f64`:
```
var height: Meters = ...;
var scale: f32 = 1.25;
// ✅ Allowed: `scale` implicitly converted
// from `f32` to `f64`.
var allowed: Meters = height.Scale(scale);
// ❌ Illegal: `Meters` doesn't implement
// `MultipliableWith(f32)`.
var illegal: Meters = height * scale;
```
The workaround is to define a parameterized implementation that performs the
conversion. The implementation is for types that implement the
[`ImplicitAs` interface](/docs/design/expressions/implicit_conversions.md#extensibility).
```
// "Implementation Two"
external impl forall [T:! ImplicitAs(f64)]
Meters as MultipliableWith(T) where .Result = Meters {
fn Multiply[self: Self](other: T) -> Result {
// Carbon will implicitly convert `other` from type
// `T` to `f64` to perform this call.
return self.(Meters.(MultipliableWith(f64).Multiply))(other);
}
}
// ✅ Allowed: uses `Meters as MultipliableWith(T)` impl
// with `T == f32` since `f32 impls ImplicitAs(f64)`.
var now_allowed: Meters = height * scale;
```
Observe that the [prioritization rule](#prioritization-rule) will still prefer
the unparameterized impl when there is an exact match.
To reduce the boilerplate needed to support these implicit conversions when
defining operator overloads, Carbon has the `like` operator. This operator can
only be used in the type or type-of-type part of an `impl` declaration, as part
of a forward declaration or definition, in a place of a type.
```
// Notice `f64` has been replaced by `like f64`
// compared to "implementation one" above.
external impl Meters as MultipliableWith(like f64)
where .Result = Meters {
fn Multiply[self: Self](other: f64) -> Result {
return self.Scale(other);
}
}
```
This `impl` definition actually defines two implementations. The first is the
same as this definition with `like f64` replaced by `f64`, giving something
equivalent to "implementation one". The second implementation replaces the
`like f64` with a parameter that ranges over types that can be implicitly
converted to `f64`, equivalent to "implementation two".
In general, each `like` adds one additional parameterized implementation. There
is always the impl defined with all of the `like` expressions replaced by their
arguments with the definition supplied in the source code. In addition, for each
`like` expression, there is an automatic `impl` definition with it replaced by a
new parameter. These additional automatic implementations will delegate to the
main impl, which will trigger implicit conversions according to
[Carbon's ordinary implicit conversion rules](/docs/design/expressions/implicit_conversions.md).
In this example, there are two uses of `like`, producing three implementations
```
external impl like Meters as MultipliableWith(like f64)
where .Result = Meters {
fn Multiply[self: Self](other: f64) -> Result {
return self.Scale(other);
}
}
```
is equivalent to "implementation one", "implementation two", and:
```
external impl forall [T:! ImplicitAs(Meters)]
T as MultipliableWith(f64) where .Result = Meters {
fn Multiply[self: Self](other: f64) -> Result {
// Will implicitly convert `self` to `Meters` in order to
// match the signature of this `Multiply` method.
return self.(Meters.(MultipliableWith(f64).Multiply))(other);
}
}
```
`like` may be used in forward declarations in a way analogous to impl
definitions.
```
external impl like Meters as MultipliableWith(like f64)
where .Result = Meters;
}
```
is equivalent to:
```
// All `like`s removed. Same as the declaration part of
// "implementation one", without the body of the definition.
external impl Meters as MultipliableWith(f64)
where .Result = Meters;
// First `like` replaced with a wildcard.
external impl forall [T:! ImplicitAs(Meters)]
T as MultipliableWith(f64) where .Result = Meters;
// Second `like` replaced with a wildcard. Same as the
// declaration part of "implementation two", without the
// body of the definition.
external impl forall [T:! ImplicitAs(f64)]
Meters as MultipliableWith(T) where .Result = Meters;
```
In addition, the generated impl definition for a `like` is implicitly injected
at the end of the (unique) source file in which the impl is first declared. That
is, it is injected in the API file if the impl is declared in an API file, and
in the sole impl file declaring the impl otherwise. This means an `impl`
declaration using `like` in an API file also makes the parameterized definition
If one `impl` declaration uses `like`, other declarations must use `like` in the
same way to match.
The `like` operator may be nested, as in:
```
external impl like Vector(like String) as Printable;
```
Which will generate implementations with declarations:
```
external impl Vector(String) as Printable;
external impl forall [T:! ImplicitAs(Vector(String))] T as Printable;
external impl forall [T:! ImplicitAs(String)] Vector(T) as Printable;
```
The generated implementations must be legal or the `like` is illegal. For
example, it must be legal to have those `impl` definitions in this library by
the [orphan rule](#orphan-rule). In addition, the generated `impl` definitions
must only require implicit conversions that are guaranteed to exist. For
example, there existing an implicit conversion from `T` to `String` does not
imply that there is one from `Vector(T)` to `Vector(String)`, so the following
use of `like` is illegal:
```
// ❌ Illegal: Can't convert a value with type
// `Vector(T:! ImplicitAs(String))`
// to `Vector(String)` for `self`
// parameter of `Printable.Print`.
external impl Vector(like String) as Printable;
```
Since the additional implementation definitions are generated eagerly, these
errors will be reported in the file with the first declaration.
The argument to `like` must either not mention any type parameters, or those
parameters must be able to be determined due to being repeated outside of the
`like` expression.
```
// ✅ Allowed: no parameters
external impl like Meters as Printable;
// ❌ Illegal: No other way to determine `T`
external impl forall [T:! IntLike] like T as Printable;
// ❌ Illegal: `T` being used in a `where` clause
// is insufficient.
external impl forall [T:! IntLike] like T
as MultipliableWith(i64) where .Result = T;
// ❌ Illegal: `like` can't be used in a `where`
// clause.
external impl Meters as MultipliableWith(f64)
where .Result = like Meters;
// ✅ Allowed: `T` can be determined by another
// part of the query.
external impl forall [T:! IntLike] like T
as MultipliableWith(T) where .Result = T;
external impl forall [T:! IntLike] T
as MultipliableWith(like T) where .Result = T;
// ✅ Allowed: Only one `like` used at a time, so this
// is equivalent to the above two examples.
external impl forall [T:! IntLike] like T
as MultipliableWith(like T) where .Result = T;
```
## Parameterized types
Types may have generic parameters. Those parameters may be used to specify types
in the declarations of its members, such as data fields, member functions, and
even interfaces being implemented. For example, a container type might be
parameterized by the type of its elements:
```
class HashMap(
KeyType:! Hashable & EqualityComparable & Movable,
ValueType:! Movable) {
// `Self` is `HashMap(KeyType, ValueType)`.
// Parameters may be used in function signatures.
fn Insert[addr self: Self*](k: KeyType, v: ValueType);
// Parameters may be used in field types.
private var buckets: Vector((KeyType, ValueType));
// Parameters may be used in interfaces implemented.
impl as Container where .ElementType = (KeyType, ValueType);
impl as ComparableWith(HashMap(KeyType, ValueType));
}
```
Note that, unlike functions, every parameter to a type must either be generic or
template, using `:!` or `template...:!`, not dynamic, with a plain `:`.
Two types are the same if they have the same name and the same arguments.
Carbon's [manual type equality](#manual-type-equality) approach means that the
compiler may not always be able to tell when two type expressions are equal
without help from the user, in the form of
[`observe` declarations](#observe-declarations). This means Carbon will not in
general be able to determine when types are unequal.
Unlike an [interface's parameters](#parameterized-interfaces), a type's
parameters may be [deduced](terminology.md#deduced-parameter), as in:
```
fn ContainsKey[KeyType:! Movable, ValueType:! Movable]
(haystack: HashMap(KeyType, ValueType), needle: KeyType)
-> bool { ... }
fn MyMapContains(s: String) {
var map: HashMap(String, i32) = (("foo", 3), ("bar", 5));
// ✅ Deduces `KeyType` = `String` from the types of both arguments.
// Deduces `ValueType` = `i32` from the type of the first argument.
return ContainsKey(map, s);
}
```
Note that restrictions on the type's parameters from the type's declaration can
be [implied constraints](#implied-constraints) on the function's parameters.
### Specialization
[Specialization](terminology.md#generic-specialization) is used to improve
performance in specific cases when a general strategy would be inefficient. For
example, you might use
[binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm) for
containers that support random access and keep their contents in sorted order
but [linear search](https://en.wikipedia.org/wiki/Linear_search) in other cases.
Types, like functions, may not be specialized directly in Carbon. This effect
can be achieved, however, through delegation.
For example, imagine we have a parameterized class `Optional(T)` that has a
default storage strategy that works for all `T`, but for some types we have a
more efficient approach. For pointers we can use a
[null value](https://en.wikipedia.org/wiki/Null_pointer) to represent "no
pointer", and for booleans we can support `True`, `False`, and `None` in a
single byte. Clients of the optional library may want to add additional
specializations for their own types. We make an interface that represents "the
storage of `Optional(T)` for type `T`," written here as `OptionalStorage`:
```
interface OptionalStorage {
let Storage:! type;
fn MakeNone() -> Storage;
fn Make(x: Self) -> Storage;
fn IsNone(x: Storage) -> bool;
fn Unwrap(x: Storage) -> Self;
}
```
The default implementation of this interface is provided by a
[blanket implementation](#blanket-impl-declarations):
```
// Default blanket implementation
impl forall [T:! Movable] T as OptionalStorage
where .Storage = (bool, T) {
...
}
```
This implementation can then be
[specialized](#lookup-resolution-and-specialization) for more specific type
patterns:
```
// Specialization for pointers, using nullptr == None
final external impl forall [T:! type] T* as OptionalStorage
where .Storage = Array(Byte, sizeof(T*)) {
...
}
// Specialization for type `bool`.
final external impl bool as OptionalStorage
where .Storage = Byte {
...
}
```
Further, libraries can implement `OptionalStorage` for their own types, assuming
the interface is not marked `private`. Then the implementation of `Optional(T)`
can delegate to `OptionalStorage` for anything that can vary with `T`:
```
class Optional(T:! Movable) {
fn None() -> Self {
return {.storage = T.(OptionalStorage.MakeNone)()};
}
fn Some(x: T) -> Self {
return {.storage = T.(OptionalStorage.Make)(x)};
}
...
private var storage: T.(OptionalStorage.Storage);
}
```
Note that the constraint on `T` is just `Movable`, not
`Movable & OptionalStorage`, since the `Movable` requirement is
[sufficient to guarantee](#lookup-resolution-and-specialization) that some
implementation of `OptionalStorage` exists for `T`. Carbon does not require
callers of `Optional`, even generic callers, to specify that the argument type
implements `OptionalStorage`:
```
// ✅ Allowed: `T` just needs to be `Movable` to form `Optional(T)`.
// A `T:! OptionalStorage` constraint is not required.
fn First[T:! Movable & Eq](v: Vector(T)) -> Optional(T);
```
Adding `OptionalStorage` to the constraints on the parameter to `Optional` would
obscure what types can be used as arguments. `OptionalStorage` is an
implementation detail of `Optional` and need not appear in its public API.
In this example, a `let` is used to avoid repeating `OptionalStorage` in the
definition of `Optional`, since it has no name conflicts with the members of
`Movable`:
```
class Optional(T:! Movable) {
private let U:! Movable & OptionalStorage = T;
fn None() -> Self {
return {.storage = U.MakeNone()};
}
fn Some(x: T) -> Self {
return {.storage = U.Make(x)};
}
...
private var storage: U.Storage;
}
```
## Future work
### Dynamic types
Generics provide enough structure to support runtime dispatch for values with
types that vary at runtime, without giving up type safety. Both Rust and Swift
have demonstrated the value of this feature.
#### Runtime type parameters
This feature is about allowing a function's type parameter to be passed in as a
dynamic (non-generic) parameter. All values of that type would still be required
to have the same type.
#### Runtime type fields
Instead of passing in a single type parameter to a function, we could store a
type per value. This changes the data layout of the value, and so is a somewhat
more invasive change. It also means that when a function operates on multiple
values they could have different real types.
### Abstract return types
This lets you return an anonymous type implementing an interface from a
function. In Rust this is the
[`impl Trait` return type](https://rust-lang.github.io/rfcs/1522-conservative-impl-trait.html).
In Swift, there are discussions about implementing this feature under the name
"reverse generics" or "opaque result types":
[1](https://forums.swift.org/t/improving-the-ui-of-generics/22814#heading--reverse-generics),
[2](https://forums.swift.org/t/reverse-generics-and-opaque-result-types/21608),
[3](https://forums.swift.org/t/se-0244-opaque-result-types/21252),
[4](https://forums.swift.org/t/se-0244-opaque-result-types-reopened/22942),
Swift is considering spelling this `<V: Collection> V` or `some Collection`.
### Evolution
There are a collection of use cases for making different changes to interfaces
that are already in use. These should be addressed either by describing how they
can be accomplished with existing generics features, or by adding features.
In addition, evolution from (C++ or Carbon) templates to generics needs to be
supported and made safe.
### Testing
The idea is that you would write tests alongside an interface that validate the
expected behavior of any type implementing that interface.
### Impl with state
A feature we might consider where an `impl` itself can have state.
### Generic associated types and higher-ranked types
This would be some way to express the requirement that there is a way to go from
a type to an implementation of an interface parameterized by that type.
#### Generic associated types
Generic associated types are about when this is a requirement of an interface.
These are also called "associated type constructors."
Rust has
[stabilized this feature](https://github.com/rust-lang/rust/pull/96709).
#### Higher-ranked types
Higher-ranked types are used to represent this requirement in a function
signature. They can be
[emulated using generic associated types](https://smallcultfollowing.com/babysteps//blog/2016/11/03/associated-type-constructors-part-2-family-traits/).
### Field requirements
We might want to allow interfaces to express the requirement that any
implementing type has a particular field. This would be to match the
expressivity of inheritance, which can express "all subtypes start with this
list of fields."
### Bridge for C++ customization points
See details in [the goals document](goals.md#bridge-for-c-customization-points).
### Variadic arguments
Some facility for allowing a function to generically take a variable number of
arguments.
### Range constraints on generic integers
We currently only support `where` clauses on type-of-types. We may want to also
support constraints on generic integers. The constraint with the most expected
value is the ability to do comparisons like `<`, or `>=`. For example, you might
constrain the `N` member of [`NSpacePoint`](#associated-constants) using an
expression like `PointT:! NSpacePoint where 2 <= .N and .N <= 3`.
The concern here is supporting this at compile time with more benefit than
complexity. For example, we probably don't want to support integer-range based
types at runtime, and there are also concerns about reasoning about comparisons
between multiple generic integer parameters. For example, if `J < K` and
`K <= L`, can we call a function that requires `J < L`? There is also a
secondary syntactic concern about how to write this kind of constraint on a
parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
## References
- [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553)
- [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
- [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818)
- [#931: Generic impls access (details 4)](https://github.com/carbon-language/carbon-lang/pull/931)
- [#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920)
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
- [#983: Generic details 7: final impls](https://github.com/carbon-language/carbon-lang/pull/983)
- [#990: Generics details 8: interface default and final members](https://github.com/carbon-language/carbon-lang/pull/990)
- [#1013: Generics: Set associated constants using `where` constraints](https://github.com/carbon-language/carbon-lang/pull/1013)
- [#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084)
- [#1088: Generic details 10: interface-implemented requirements](https://github.com/carbon-language/carbon-lang/pull/1088)
- [#1144: Generic details 11: operator overloading](https://github.com/carbon-language/carbon-lang/pull/1144)
- [#1146: Generic details 12: parameterized types](https://github.com/carbon-language/carbon-lang/pull/1146)
- [#1327: Generics: `impl forall`](https://github.com/carbon-language/carbon-lang/pull/1327)
- [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107)
- [#2347: What can be done with an incomplete interface](https://github.com/carbon-language/carbon-lang/pull/2347)
- [#2376: Constraints must use `Self`](https://github.com/carbon-language/carbon-lang/pull/2376)
- [#2483: Replace keyword `is` with `impls`](https://github.com/carbon-language/carbon-lang/pull/2483)