Skip to content

Commit

Permalink
Constraints for generics (generics details 3) (#818)
Browse files Browse the repository at this point in the history
This proposal describes `where` clauses that can add constraints on a type-of-type, for example define restrictions on its associated types. Example:

```
fn FindFirstPrime[T:! Container where .Element = i32]
    (c: T) -> Optional(i32) {
  // The elements of `c` have type `T.Element`, which is `i32`.
  ...
}

fn PrintContainer[T:! Container where .Element is Printable](c: T) {
  // The type of the elements of `c` is not known, but we do know
  // that type satisfies the `Printable` interface.
  ...
}
```

Some other constraints, such as `Sized` are defined as type-of-types directly, possibly parameterized.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
  • Loading branch information
3 people authored Oct 30, 2021
1 parent 05efb27 commit 89a829b
Show file tree
Hide file tree
Showing 7 changed files with 2,042 additions and 84 deletions.
1,295 changes: 1,227 additions & 68 deletions docs/design/generics/details.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs/design/generics/goals.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,8 @@ are complicated and

### Interfaces are nominal

Interfaces can either be structural, as in Go, or nominal, as in Rust and Swift.
Interfaces can either be [structural](terminology.md#structural-interfaces), as
in Go, or [nominal](terminology.md#nominal-interfaces), as in Rust and Swift.
Structural interfaces match any type that has the required methods, whereas
nominal interfaces only match if there is an explicit declaration stating that
the interface is implemented for that specific type. Carbon will support nominal
Expand Down
49 changes: 36 additions & 13 deletions docs/design/generics/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ pointers to other design documents that dive deeper into individual topics.
- [Generic type parameters](#generic-type-parameters)
- [Requiring or extending another interface](#requiring-or-extending-another-interface)
- [Combining interfaces](#combining-interfaces)
- [Structural interfaces](#structural-interfaces)
- [Named constraints](#named-constraints)
- [Type erasure](#type-erasure)
- [Adapting types](#adapting-types)
- [Interface input and output types](#interface-input-and-output-types)
- [Associated types](#associated-types)
- [Parameterized interfaces](#parameterized-interfaces)
- [Constraints](#constraints)
- [Future work](#future-work)
- [References](#references)

Expand Down Expand Up @@ -84,9 +85,9 @@ Summary of how Carbon generics work:
- The `&` operation on type-of-types allows you conveniently combine
interfaces. It gives you all the names that don't conflict.
- You may also declare a new type-of-type directly using
["structural interfaces"](terminology.md#structural-interfaces). Structural
interfaces can express requirements that multiple interfaces be implemented,
and give you control over how name conflicts are handled.
["named constraints"](terminology.md#named-constraints). Named constraints
can express requirements that multiple interfaces be implemented, and give
you control over how name conflicts are handled.
- Alternatively, you may resolve name conflicts by using a qualified syntax to
directly call a function from a specific interface.

Expand Down Expand Up @@ -251,7 +252,7 @@ the constraint on the type is that it must implement the interface `Comparable`.
A type-of-type also defines a set of names and a mapping to corresponding
qualified names. You may combine interfaces into new type-of-types using
[the `&` operator](#combining-interfaces) or
[structural interfaces](#structural-interfaces).
[named constraints](#named-constraints).

### Generic functions

Expand Down Expand Up @@ -406,16 +407,16 @@ fn BothDraws[T:! Renderable & EndOfGame](game_state: T*) {
}
```

#### Structural interfaces
#### Named constraints

You may also declare a new type-of-type directly using
["structural interfaces"](terminology.md#structural-interfaces). Structural
interfaces can express requirements that multiple interfaces be implemented, and
give you control over how name conflicts are handled. Structural interfaces have
other applications and capabilities not covered here.
["named constraints"](terminology.md#named-constraints). Named constraints can
express requirements that multiple interfaces be implemented, and give you
control over how name conflicts are handled. Named constraints have other
applications and capabilities not covered here.

```
structural interface Combined {
constraint Combined {
impl as Renderable;
impl as EndOfGame;
alias Draw_Renderable = Renderable.Draw;
Expand All @@ -431,7 +432,7 @@ fn CallItAll[T:! Combined](game_state: T*, int winner) {
}
game_state->Draw_Renderable();
// Can still use qualified syntax for names
// not defined in the structural interface
// not defined in the named constraint
return game_state->(Renderable.Center)();
}
```
Expand Down Expand Up @@ -561,9 +562,30 @@ fn FindInVector[T:! Type, U:! Equatable(T)](v: Vector(T), needle: U)
fn CompileError[T:! Type, U:! Equatable(T)](x: U) -> T;
```

### Constraints

Type-of-types can be further constrained using a `where` clause:

```
fn FindFirstPrime[T:! Container where .Element == i32]
(c: T, i: i32) -> Optional(i32) {
// The elements of `c` have type `T.Element`, which is `i32`.
...
}
fn PrintContainer[T:! Container where .Element is Printable](c: T) {
// The type of the elements of `c` is not known, but we do know
// that type satisfies the `Printable` interface.
...
}
```

Constraints limit the types that the generic function can operate on, but
increase the knowledge that may be used in the body of the function to operate
on values of those types.

## Future work

- Other kinds of constraints will be finalized.
- Implementations can be parameterized to apply to multiple types. These
implementations would be restricted to various conditions are true for the
parameters. When there are two implementations that can apply, there is a
Expand All @@ -583,3 +605,4 @@ fn CompileError[T:! Type, U:! Equatable(T)](x: U) -> T;

- [#524: Generics overview](https://github.com/carbon-language/carbon-lang/pull/524)
- [#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)
15 changes: 13 additions & 2 deletions docs/design/generics/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Interface](#interface)
- [Structural interfaces](#structural-interfaces)
- [Nominal interfaces](#nominal-interfaces)
- [Named constraints](#named-constraints)
- [Associated entity](#associated-entity)
- [Impls: Implementations of interfaces](#impls-implementations-of-interfaces)
- [Compatible types](#compatible-types)
Expand Down Expand Up @@ -298,6 +299,14 @@ We use the "structural" versus "nominal" terminology as a generalization of the
same terms being used in a
[subtyping context](https://en.wikipedia.org/wiki/Subtyping#Subtyping_schemes).

### Named constraints

Named constraints are "structural" in the sense that they match a type based on
meeting some criteria rather than an explicit statement in the type's
definition. The criteria for a named constraint, however, are less focused on
the type's API and instead might include a set of nominal interfaces that the
type must implement.

## Associated entity

An _associated entity_ is a requirement in an interface that a type's
Expand All @@ -320,8 +329,10 @@ instead of associated entity.
An _impl_ is an implementation of an interface for a specific type. It is the
place where the function bodies are defined, values for associated types, etc.
are given. Impls are needed for [nominal interfaces](#nominal-interfaces);
[structural interfaces](#structural-interfaces) define conformance implicitly
instead of by requiring an impl to be defined.
[structural interfaces](#structural-interfaces) and
[named constraints](#named-constraints) define conformance implicitly instead of
by requiring an impl to be defined. In can still make sense to implement a named
constraint as a way to implement all of the interfaces it requires.

## Compatible types

Expand Down
3 changes: 3 additions & 0 deletions docs/design/lexical_conventions/words.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The following words are interpreted as keywords:
- `break`
- `case`
- `class`
- `constraint`
- `continue`
- `default`
- `else`
Expand All @@ -55,11 +56,13 @@ The following words are interpreted as keywords:
- `impl`
- `import`
- `interface`
- `is`
- `let`
- `library`
- `match`
- `namespace`
- `not`
- `observe`
- `or`
- `override`
- `package`
Expand Down
Loading

0 comments on commit 89a829b

Please sign in to comment.