We want to Carbon to have a high quality generics feature that achieves the goals set out in #24. This is too big to land in a single proposal. This proposal goes into the details of the core of the feature, and provides an outline covering future work. It covers:
- interfaces
- implementing interfaces for types
- resolving name conflicts
- facet types
- type-types as the way of describing type variables
- structural interfaces
- combining interfaces
- interface requirements and extension
- type compatibility
This is a follow on to these previous generics proposals:
The content for this proposal was extracted from a larger Generics combined draft proposal.
This is a proposal to add this detailed design document.
Much of this rationale was captured in the Generics goals proposal.
The interface implementation syntax was decided in question-for-leads issue #575.
struct Song {
// data and methods ...
impl as Printable {
method (me: Self) Print() { ... }
}
}
external impl Song as Comparable { ... }
This proposal includes additional discussion and additional alternatives.
In this option, the interface name comes before the type name.
struct Song { ... }
external impl Comparable for Song { ... }
Advantage:
- This ordering used by Rust.
Disadvantages:
- We prefer the type name before the interface name (using
as
), since having the type first and outer is consistent with those implemented instruct
declarations. It also seems more natural to express the parameters to the interface in terms of the parameters and associated items of the type than the other way around. - The
Song as Comparable
phrase is the name of the facet type that is being implemented.
struct Song {
// data and methods ...
impl Printable {
method (me: Self) Print() { ... }
}
}
Advantage:
- More concise, so less to read and write.
Disadvantage:
- Less consistent with the
external impl
syntax. - Less consistent with the planned inline conditional impl syntax.
struct Song { ... }
impl Song as Comparable { ... }
Advantage:
- More concise, so less to read and write.
Disadvantages:
- Less explicit that the methods of this impl definition are not contributing to unqualified API of the type.
- This kind of implementation is naturally referred to as "external", especially when contrasting with "inline impl".
We considered an out-of-line syntax for declaring and defining interface impl
blocks, to be consistent with the external impl
declarations. For example:
struct Song { ... }
impl Printable for Song { ... }
external impl Comparable for Song { ... }
The main advantage of this syntax was that it was uniform across many cases, including conditional conformance. It wasn't ideal across a number of dimensions though.
- It repeated the type name which was redundant and verbose
- It could affect the API of the type outside of the type definition.
Instead of the external impl
statement, we considered putting all external
implementations in an expand
block.
struct Song {
impl Printable { ... }
}
expand Song {
impl Comparable { ... }
}
Advantages:
- This option is most similar to the approach used by Swift.
- Easier to copy-paste an
impl
between astruct
definition and anexpand
block.
The expand
approach had some disadvantages:
- Implementations were indented more than the
external impl
approach. - Extra ceremony in the case of only implementing one type for an interface. This case is expected to be common since external implementations will most often be defined with the interface.
- When implementing multiple interfaces in a single
expand
block, the name of the type being expanded could be far from theimpl
declaration and hard to find.
We originally used extend
instead of expand
but that collided with using
extends
for interface extension and derived classes.
Other alternatives considered will be in a future proposal. Some of them can be seen in a rough form in #36.