mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 15:10:12 +01:00
Update generics goals doc (#3060)
This reflects changes from #2138 , plus a number of other updates, including reflecting that we have decided to include templates in Carbon.
This commit is contained in:
+159
-161
@@ -14,13 +14,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Background](#background)
|
||||
- [Generic parameters](#generic-parameters)
|
||||
- [Interfaces](#interfaces)
|
||||
- [Relationship to templates](#relationship-to-templates)
|
||||
- [Goals](#goals)
|
||||
- [Templates](#templates)
|
||||
- [Checked-generic goals](#checked-generic-goals)
|
||||
- [Use cases](#use-cases)
|
||||
- [Generic programming](#generic-programming)
|
||||
- [Upgrade path from C++ abstract interfaces](#upgrade-path-from-c-abstract-interfaces)
|
||||
- [Dependency injection](#dependency-injection)
|
||||
- [Generics instead of open overloading and ADL](#generics-instead-of-open-overloading-and-adl)
|
||||
- [Checked generics instead of open overloading and ADL](#checked-generics-instead-of-open-overloading-and-adl)
|
||||
- [Performance](#performance)
|
||||
- [Better compiler experience](#better-compiler-experience)
|
||||
- [Encapsulation](#encapsulation)
|
||||
@@ -34,11 +34,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Interfaces are nominal](#interfaces-are-nominal)
|
||||
- [Interop and evolution](#interop-and-evolution)
|
||||
- [Bridge for C++ customization points](#bridge-for-c-customization-points)
|
||||
- [What we are not doing](#what-we-are-not-doing)
|
||||
- [What we are not doing with checked generics](#what-we-are-not-doing-with-checked-generics)
|
||||
- [Not the full flexibility of templates](#not-the-full-flexibility-of-templates)
|
||||
- [Template use cases that are out of scope](#template-use-cases-that-are-out-of-scope)
|
||||
- [Generics will be checked when defined](#generics-will-be-checked-when-defined)
|
||||
- [Specialization strategy](#specialization-strategy)
|
||||
- [Checked generics will be checked when defined](#checked-generics-will-be-checked-when-defined)
|
||||
- [Implementation strategy](#implementation-strategy)
|
||||
- [References](#references)
|
||||
|
||||
<!-- tocstop -->
|
||||
@@ -62,7 +61,8 @@ Carbon's checked generics will feature
|
||||
[early type checking](terminology.md#early-versus-late-type-checking) and
|
||||
[complete definition checking](terminology.md#complete-definition-checking).
|
||||
|
||||
Carbon's template generics, in contrast, will more closely follow the
|
||||
Carbon's [template generics](#templates), in contrast, will more closely follow
|
||||
the
|
||||
[compile-time duck typing](https://en.wikipedia.org/wiki/Duck_typing#Templates_or_generic_types)
|
||||
approach of C++ templates.
|
||||
|
||||
@@ -105,7 +105,7 @@ languages:
|
||||
(compile-time only)
|
||||
- [Abstract base classes](<https://en.wikipedia.org/wiki/Class_(computer_programming)#Abstract_and_concrete>)
|
||||
in C++, etc. (run-time only)
|
||||
- [Go interfaces](https://gobyexample.com/interfaces) (run-time only)
|
||||
- [Go interfaces](https://gobyexample.com/interfaces)
|
||||
|
||||
In addition to specifying the methods available on a type, we may in the future
|
||||
expand the role of interfaces to allow other type constraints, such as on size,
|
||||
@@ -116,7 +116,7 @@ instead of, rather than in addition to, standard inheritance-and-classes
|
||||
object-oriented language support. For the moment, everything beyond specifying
|
||||
the _methods_ available is out of scope.
|
||||
|
||||
### Relationship to templates
|
||||
## Templates
|
||||
|
||||
The entire idea of statically typed languages is that coding against specific
|
||||
types and interfaces is a better model and experience. Unfortunately, templates
|
||||
@@ -124,37 +124,37 @@ don't provide many of those benefits to programmers until it's too late, when
|
||||
users are consuming the API. Templates also come with high overhead, such as
|
||||
[template error messages](#better-compiler-experience).
|
||||
|
||||
We want Carbon code to move towards more rigorously type checked constructs.
|
||||
We want Carbon code to move towards more rigorously type-checked constructs.
|
||||
However, existing C++ code is full of unrestricted usage of compile-time
|
||||
duck-typed templates. They are incredibly convenient to write and so likely will
|
||||
continue to exist for a long time.
|
||||
|
||||
The question of whether Carbon has direct support for templates is out of scope
|
||||
for this document. The generics design is not completely separate from
|
||||
templates, so it is written as if Carbon will have its own templating system. It
|
||||
is assumed to be similar to C++ templates with some specific changes:
|
||||
Carbon will have direct support for templates in addition to checked generics.
|
||||
Carbon's template system will be similar to C++ templates with some specific
|
||||
changes:
|
||||
|
||||
- It may have some limitations to be more compatible with generics, much like
|
||||
how we
|
||||
[restrict overloading](#generics-instead-of-open-overloading-and-adl).
|
||||
- It may have some limitations to be more compatible with checked generics,
|
||||
much like how we
|
||||
[restrict overloading](#checked-generics-instead-of-open-overloading-and-adl).
|
||||
- We likely will have a different method of selecting between different
|
||||
template instantiations, since
|
||||
[SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error)
|
||||
makes it difficult to deliver high quality compiler diagnostics.
|
||||
|
||||
We assume Carbon will have templates for a few different reasons:
|
||||
Other aspects of Carbon will reduce the number of situations where errors will
|
||||
only be detected after the template is
|
||||
[instantiated](terminology.md#instantiation):
|
||||
|
||||
- Carbon generics will definitely have to interact with _C++_ templates, and
|
||||
many of the issues will be similar.
|
||||
- We want to leave room in the design for templates, since it seems like it
|
||||
would be easier to remove templates if they are not pulling their weight
|
||||
than figure out how to add them in if they turn out to be needed.
|
||||
- We may want to have templates in Carbon as a temporary measure, to make it
|
||||
easier for users to transition off of C++ templates.
|
||||
- Carbon's grammar won't require type information to disambiguate parsing, and
|
||||
so definitions may be parsed without knowing the values of template
|
||||
arguments.
|
||||
- Carbon's name resolution is more restricted. Every package has its own
|
||||
namespace, and there will be no
|
||||
[argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl).
|
||||
|
||||
## Goals
|
||||
## Checked-generic goals
|
||||
|
||||
Our goal for generics support in Carbon is to get most of the expressive
|
||||
Our goal for checked generics support in Carbon is to get most of the expressive
|
||||
benefits of C++ templates and open overloading with fewer downsides.
|
||||
Additionally, we want to support some dynamic dispatch use cases; for example,
|
||||
in cases that inheritance struggles with.
|
||||
@@ -162,7 +162,7 @@ in cases that inheritance struggles with.
|
||||
### Use cases
|
||||
|
||||
To clarify the expressive range we are aiming for, here are some specific use
|
||||
cases we expect Carbon generics to cover.
|
||||
cases we expect Carbon checked generics to cover.
|
||||
|
||||
#### Generic programming
|
||||
|
||||
@@ -183,18 +183,19 @@ generally be used with [static dispatch](#dispatch-control).
|
||||
|
||||
#### Upgrade path from C++ abstract interfaces
|
||||
|
||||
Interfaces in C++ are often represented by abstract base classes. Generics
|
||||
should offer an alternative that does not rely on inheritance. This means looser
|
||||
coupling and none of the problems of multiple inheritance. Some people, such as
|
||||
Interfaces in C++ are often represented by abstract base classes. Checked
|
||||
generics should offer an alternative that does not rely on inheritance. This
|
||||
means looser coupling and none of the problems of multiple inheritance. Some
|
||||
people, such as
|
||||
[Sean Parent](https://sean-parent.stlab.cc/papers-and-presentations/#better-code-runtime-polymorphism),
|
||||
advocate for runtime polymorphism patterns in C++ that avoid inheritance because
|
||||
it can cause runtime performance, correctness, and code maintenance problems in
|
||||
some situations. Those patterns require a lot of boilerplate and complexity in
|
||||
C++. It would be nice if those patterns were simpler to express with Carbon
|
||||
generics. More generally, Carbon generics will provide an alternative for those
|
||||
situations inheritance doesn't handle as well. As a specific example, we would
|
||||
like Carbon generics to supplant the need to support multiple inheritance in
|
||||
Carbon.
|
||||
checked generics. More generally, Carbon checked generics will provide an
|
||||
alternative for those situations inheritance doesn't handle as well. As a
|
||||
specific example, we would like Carbon checked generics to supplant the need to
|
||||
support multiple inheritance in Carbon.
|
||||
|
||||
This is a case that would use [dynamic dispatch](#dispatch-control).
|
||||
|
||||
@@ -202,15 +203,16 @@ This is a case that would use [dynamic dispatch](#dispatch-control).
|
||||
|
||||
Types which only support subclassing for test stubs and mocks, as in
|
||||
["dependency injection"](https://en.wikipedia.org/wiki/Dependency_injection),
|
||||
should be able to easily migrate to generics. This extends outside the realm of
|
||||
testing, allowing general configuration of how dependencies can be satisfied.
|
||||
For example, generics might be used to configure how a library writes logs.
|
||||
should be able to easily migrate to checked generics. This extends outside the
|
||||
realm of testing, allowing general configuration of how dependencies can be
|
||||
satisfied. For example, checked generics might be used to configure how a
|
||||
library writes logs.
|
||||
|
||||
This would allow you to avoid the runtime overhead of virtual functions, using
|
||||
[static dispatch](#dispatch-control) without the
|
||||
[poor build experience of templates](#better-compiler-experience).
|
||||
|
||||
#### Generics instead of open overloading and ADL
|
||||
#### Checked generics instead of open overloading and ADL
|
||||
|
||||
One name lookup problem we would like to avoid is caused by open overloading.
|
||||
Overloading is where you provide multiple implementations of a function with the
|
||||
@@ -226,20 +228,22 @@ function was originally defined. Together these enable
|
||||
This is commonly used to provide a type-specific implementation of some
|
||||
operation, but doesn't provide any enforcement of consistency across the
|
||||
different overloads. It makes the meaning of code dependent on which overloads
|
||||
are imported, and is at odds with being able to type check a function
|
||||
generically.
|
||||
are imported, and is at odds with being able to type check a function's
|
||||
definition before instantiation.
|
||||
|
||||
Our goal is to address this use case, known more generally as
|
||||
[the expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions),
|
||||
with a generics mechanism that does enforce consistency so that type checking is
|
||||
possible without seeing all implementations. This will be Carbon's replacement
|
||||
for open overloading. As a consequence, Carbon generics will need to be able to
|
||||
support operator overloading.
|
||||
with a checked-generics mechanism that does enforce consistency so that type
|
||||
checking is possible without seeing all implementations. This will be Carbon's
|
||||
replacement for open overloading. This is encapsulated in Carbon's
|
||||
["one static open extension mechanism" principle](/docs/project/principles/static_open_extension.md).
|
||||
As a consequence, Carbon checked generics will need to be able to support
|
||||
operator overloading.
|
||||
|
||||
A specific example is the absolute value function `Abs`. We would like to write
|
||||
`Abs(x)` for a variety of types. For some types `T`, such as `Int32` or
|
||||
`Float64`, the return type will be the same `T`. For other types, such as
|
||||
`Complex64` or `Quaternion`, the return type will be different. The generic
|
||||
`Complex64` or `Quaternion`, the return type will be different. Checked generic
|
||||
functions that call `Abs` will need a way to specify whether they only operate
|
||||
on `T` such that `Abs` has signature `T -> T`.
|
||||
|
||||
@@ -250,14 +254,14 @@ overloading, which will
|
||||
### Performance
|
||||
|
||||
For any real-world C++ template, there shall be an idiomatic reformulation in
|
||||
Carbon generics that has equal or better performance.
|
||||
Carbon checked generics that has equal or better performance.
|
||||
[Performance is the top priority for Carbon](/docs/project/goals.md#performance-critical-software),
|
||||
and we expect to use generics pervasively, and so they can't compromise that
|
||||
goal in release builds.
|
||||
and we expect to use checked generics pervasively, and so they can't compromise
|
||||
that goal in release builds.
|
||||
|
||||
**Nice to have:** There are cases where we should aim to do better than C++
|
||||
templates. For example, the additional structure of generics should make it
|
||||
easier to reduce generated code duplication, reducing code size and cache
|
||||
templates. For example, the additional structure of checked generics should make
|
||||
it easier to reduce generated code duplication, reducing code size and cache
|
||||
misses.
|
||||
|
||||
### Better compiler experience
|
||||
@@ -266,12 +270,13 @@ Compared to C++ templates, we expect to reduce build times, particularly in
|
||||
development builds. We also expect the compiler to be able to report clearer
|
||||
errors, and report them earlier in the build process.
|
||||
|
||||
One source of improvement is that the bodies of generic functions and types can
|
||||
be type checked once when they are defined, instead of every time they are used.
|
||||
This is both a reduction in the total work done, and how errors can be reported
|
||||
earlier. On use, the errors can be a lot clearer since they will be of the form
|
||||
"argument did not satisfy function's contract as stated in its signature"
|
||||
instead of "substitution failed at this line of the function's implementation."
|
||||
One source of improvement is that the bodies of checked generic functions and
|
||||
types can be type checked once when they are defined, instead of every time they
|
||||
are used. This is both a reduction in the total work done, and how errors can be
|
||||
reported earlier. On use, the errors can be a lot clearer since they will be of
|
||||
the form "argument did not satisfy function's contract as stated in its
|
||||
signature" instead of "substitution failed at this line of the function's
|
||||
implementation."
|
||||
|
||||
**Nice to have:** In development builds, we will have the option of using
|
||||
[dynamic dispatch](#dispatch-control) to reduce build times. We may also be able
|
||||
@@ -284,31 +289,30 @@ arguments or identical implementations and only generating code for them once.
|
||||
With a template, the implementation is part of the interface and types are only
|
||||
checked when the function is called and the template is instantiated.
|
||||
|
||||
A generic function is type checked when it is defined, and type checking can't
|
||||
use any information that is only known when the function is instantiated such as
|
||||
the exact argument types. Furthermore, calls to a generic function may be type
|
||||
checked using only its declaration, not its body. You should be able to call a
|
||||
generic function using only a forward declaration.
|
||||
A checked-generic function is type checked when it is defined, and type checking
|
||||
can't use any information that is only known when the function is instantiated
|
||||
such as the exact argument types. Furthermore, calls to a checked-generic
|
||||
function may be type checked using only its declaration, not its body.
|
||||
|
||||
### Predictability
|
||||
|
||||
A general property of generics is they are more predictable than templates. They
|
||||
make clear when a type satisfies the requirements of a function; they have a
|
||||
documented contract. Further, that contract is enforced by the compiler, not
|
||||
sensitive to implementation details in the function body. This eases evolution
|
||||
by reducing (but not eliminating) the impact of
|
||||
A general property of checked generics is they are more predictable than
|
||||
templates. They make clear when a type satisfies the requirements of a function;
|
||||
they have a documented contract. Further, that contract is enforced by the
|
||||
compiler, not sensitive to implementation details in the function body. This
|
||||
eases evolution by reducing (but not eliminating) the impact of
|
||||
[Hyrum's law](https://www.hyrumslaw.com/).
|
||||
|
||||
**Nice to have:** We also want well-defined boundaries between what is legal and
|
||||
not. This is "will my code be accepted by the compiler" predictability. We would
|
||||
prefer to avoid algorithms in the compiler with the form "run for up to N steps
|
||||
and report an error if it isn't resolved by then." For example, C++ compilers
|
||||
will typically have a template recursion limit. With generics, these problems
|
||||
arise due to trying to reason whether something is legal in all possible
|
||||
instantiations, rather than with specific, concrete types.
|
||||
will typically have a template recursion limit. With checked generics, these
|
||||
problems arise due to trying to reason whether something is legal in all
|
||||
possible instantiations, rather than with specific, concrete types.
|
||||
|
||||
Some of this is likely unavoidable or too costly to avoid, as most existing
|
||||
generics systems
|
||||
checked generics systems
|
||||
[have undecidable aspects to their type system](https://3fx.ch/typing-is-hard.html),
|
||||
including [Rust](https://sdleffler.github.io/RustTypeSystemTuringComplete/) and
|
||||
[Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). We
|
||||
@@ -343,13 +347,13 @@ we should invest in putting the proof search into IDEs or other tooling.
|
||||
Enable simple user control of whether to use dynamic or static dispatch.
|
||||
|
||||
**Implementation strategy:** There are two strategies for generating code for
|
||||
generic functions:
|
||||
checked-generic functions:
|
||||
|
||||
- Static specialization strategy: Like template parameters, the values for
|
||||
generic parameters must be statically known at the callsite, or known to be
|
||||
a generic parameter to the calling function. This can generate separate,
|
||||
specialized versions of each combination of generic and template arguments,
|
||||
in order to optimize for those types or values.
|
||||
- Static strategy: Like template parameters, the values for checked parameters
|
||||
must be statically known at the callsite, or known to be a generic parameter
|
||||
to the calling function. This can generate separate, specialized versions of
|
||||
each combination of checked and template arguments, in order to optimize for
|
||||
those types or values.
|
||||
- Dynamic strategy: This is when the compiler generates a single version of
|
||||
the function that uses runtime dispatch to get something semantically
|
||||
equivalent to separate instantiation, but likely with different size, build
|
||||
@@ -363,17 +367,17 @@ code analysis, specific features used in the code, or profiling -- maybe some
|
||||
specific specializations are needed for performance, but others would just be
|
||||
code bloat.
|
||||
|
||||
We require that all generic functions can be compiled using the static
|
||||
specialization strategy. For example, the values for generic parameters must be
|
||||
We require that all checked generic functions can be compiled using the static
|
||||
specialization strategy. For example, the values for checked parameters must be
|
||||
statically known at the callsite. Other limitations are
|
||||
[listed below](#specialization-strategy).
|
||||
[listed below](#implementation-strategy).
|
||||
|
||||
**Nice to have:** It is desirable that the majority of functions with generic
|
||||
**Nice to have:** It is desirable that the majority of functions with checked
|
||||
parameters also support the dynamic strategy. Specific features may prevent the
|
||||
compiler from using the dynamic strategy, but they should ideally be relatively
|
||||
rare, and easy to identify. Language features should avoid making it observable
|
||||
whether function code generated once or many times. For example, you should not
|
||||
be able to take the address of a function with generic parameters, or determine
|
||||
be able to take the address of a function with checked parameters, or determine
|
||||
if a function was instantiated more than once using function-local static
|
||||
variables.
|
||||
|
||||
@@ -382,12 +386,14 @@ limit the extent it is used automatically by implementations. For example, the
|
||||
following features would benefit substantially from guaranteed monomorphization:
|
||||
|
||||
- Field packing in class layout. For example, packing a `bool` into the lower
|
||||
bits of a pointer, or packing bit-fields with generic widths.
|
||||
bits of a pointer, or packing bit-fields with checked-parameter widths.
|
||||
- Allocating local variables in stack storage. Without monomorphization, we
|
||||
would need to perform dynamic memory allocation -- whether on the stack or
|
||||
the heap -- for local variables whose sizes depend on generic parameters.
|
||||
the heap -- for local variables whose sizes depend on checked parameters.
|
||||
- Passing parameters to functions. We cannot pass values of generic types in
|
||||
registers.
|
||||
- Finding [specialized](terminology.md#specialization) implementations of
|
||||
interfaces beyond those clearly needed from the function signature.
|
||||
|
||||
While it is possible to address these with dynamic dispatch, handling some of
|
||||
them might have far-reaching and surprising performance implications. We don't
|
||||
@@ -407,53 +413,50 @@ acceptable even when running a development or debug build.
|
||||
### Upgrade path from templates
|
||||
|
||||
We want there to be a natural, incremental upgrade path from templated code to
|
||||
generic code.
|
||||
[Assuming Carbon will support templates directly](#relationship-to-templates),
|
||||
the first step of migrating C++ template code would be to first convert it to a
|
||||
Carbon template. The problem is then how to convert templates to generics within
|
||||
Carbon. This gives us these sub-goals:
|
||||
checked-generic code. The first step of migrating C++ template code would be to
|
||||
first convert it to a [Carbon template](#templates). The problem is then how to
|
||||
convert templates to checked generics within Carbon. This gives us these
|
||||
sub-goals:
|
||||
|
||||
- Users should be able to convert a single template parameter to be generic at
|
||||
a time. A hybrid function with both template and generic parameters has all
|
||||
- Users should be able to convert a single template parameter to be checked at
|
||||
a time. A hybrid function with both template and checked parameters has all
|
||||
the limitations of a template function: it can't be completely definition
|
||||
checked, it can't use the dynamic strategy, etc. Even so, there are still
|
||||
benefits from enforcing the function's declared contract for those
|
||||
parameters that have been converted.
|
||||
- Converting from a template parameter to a generic parameter should be safe.
|
||||
- Converting from a template parameter to a checked parameter should be safe.
|
||||
It should either work or fail to compile, never silently change semantics.
|
||||
- We should minimize the effort to convert functions and types from templated
|
||||
to generic. Ideally it should just require specifying the type constraints,
|
||||
to checked. Ideally it should just require specifying the type constraints,
|
||||
affecting just the signature of the function, not its body.
|
||||
- **Nice to have:** It should be legal to call templated code from generic
|
||||
code when it would have the same semantics as if called from non-generic
|
||||
code, and an error otherwise. This is to allow more templated functions to
|
||||
be converted to generics, instead of requiring them to be converted
|
||||
specifically in bottom-up order.
|
||||
- **Nice to have:** Provide a way to migrate from a template to a generic
|
||||
without immediately updating all of the types used with the template. For
|
||||
example, if the generic code requires types to implement a new interface,
|
||||
one possible solution would use the original template code to provide an
|
||||
implementation for that interface for any type that structurally has the
|
||||
methods used by the original template.
|
||||
|
||||
If Carbon does not end up having direct support for templates, the transition
|
||||
will necessarily be less incremental.
|
||||
- **Nice to have:** It should be legal to call templated code from
|
||||
checked-generic code when it would have the same semantics as if called from
|
||||
non-generic code, and an error otherwise. This is to allow more templated
|
||||
functions to be converted to checked generics, instead of requiring them to
|
||||
be converted specifically in bottom-up order.
|
||||
- **Nice to have:** Provide a way to migrate from a template to a checked
|
||||
generic without immediately updating all of the types used with the
|
||||
template. For example, if the checked-generic code requires types to
|
||||
implement a new interface, one possible solution would use the original
|
||||
template code to provide an implementation for that interface for any type
|
||||
that structurally has the methods used by the original template.
|
||||
|
||||
### Path from regular functions
|
||||
|
||||
Replacing a regular, non-parameterized function with a generic function should
|
||||
not affect existing callers of the function. There may be some differences, such
|
||||
as when taking the address of the function, but ordinary calls should not see
|
||||
any difference. In particular, the return type of a generic function should
|
||||
match, without any type erasure or additional named members.
|
||||
Replacing a regular, non-parameterized function with a checked-generic function
|
||||
should not affect existing callers of the function. There may be some
|
||||
differences, such as when taking the address of the function, but ordinary calls
|
||||
should not see any difference. In particular, the return type of a
|
||||
checked-generic function should match, without any type erasure or additional
|
||||
named members.
|
||||
|
||||
### Coherence
|
||||
|
||||
We want the generics system to have the
|
||||
[_coherence_ property](terminology.md#coherence), so that the implementation of
|
||||
an interface for a type is well defined. Since a generic function only depends
|
||||
on interface implementations, they will always behave consistently on a given
|
||||
type, independent of context. For more on this, see
|
||||
an interface for a type is well defined. Since a checked-generic function only
|
||||
depends on interface implementations, they will always behave consistently on a
|
||||
given type, independent of context. For more on this, see
|
||||
[this description of what coherence is and why Rust enforces it](https://github.com/Ixrec/rust-orphan-rules#what-is-coherence).
|
||||
|
||||
Coherence greatly simplifies the language design, since it reduces the need for
|
||||
@@ -479,7 +482,7 @@ cases remain:
|
||||
- They should be some way of selecting between multiple implementations of an
|
||||
interface for a given type. For example, a _Song_ might support multiple
|
||||
orderings, such as by title or by artist. These would be represented by
|
||||
having multiple implementations of a _Comparable_ interface.
|
||||
having multiple implementations of a _Ordered_ interface.
|
||||
- In order to allow libraries to be composed, there must be some way of saying
|
||||
a type implements an interface that is in another package that the authors
|
||||
of the type were unaware of. This is especially important since the library
|
||||
@@ -490,7 +493,7 @@ We should have some mechanism for addressing these use cases. There are multiple
|
||||
approaches that could work:
|
||||
|
||||
- Interface implementations could be external to types and are passed in to
|
||||
generic functions separately.
|
||||
checked-generic functions separately.
|
||||
- There could be some way to create multiple types that are compatible with a
|
||||
given value that you can switch between using casts to select different
|
||||
interface implementations. This is the approach used by Rust
|
||||
@@ -518,14 +521,15 @@ the definition of `T`.
|
||||
|
||||
### Learn from others
|
||||
|
||||
Many languages have implemented generics systems, and we should learn from those
|
||||
experiences. We should copy what works and makes sense in the context of Carbon,
|
||||
and change decisions that led to undesirable compromises. We are taking the
|
||||
strongest guidance from Rust and Swift, which have similar goals and significant
|
||||
experience with the implementation and usability of generics. They both use
|
||||
nominal interfaces, were designed with generics from the start, and produce
|
||||
native code. Contrast with Go which uses structural interfaces, or Java which
|
||||
targets a virtual machine that predated its generics feature.
|
||||
Many languages have implemented checked-generics systems, and we should learn
|
||||
from those experiences. We should copy what works and makes sense in the context
|
||||
of Carbon, and change decisions that led to undesirable compromises. We are
|
||||
taking the strongest guidance from Rust and Swift, which have similar goals and
|
||||
significant experience with the implementation and usability of checked
|
||||
generics. They both use nominal interfaces, were designed with checked generics
|
||||
from the start, and produce native code. Contrast with Go which uses structural
|
||||
interfaces, or Java which targets a virtual machine that predated its generics
|
||||
feature.
|
||||
|
||||
For example, Rust has found that supporting defaults for interface methods is a
|
||||
valuable feature. It is useful for [evolution](#interop-and-evolution),
|
||||
@@ -559,8 +563,8 @@ interface, even if those methods happen to have the same signature.
|
||||
### Interop and evolution
|
||||
|
||||
[Evolution is a high priority for Carbon](/docs/project/goals.md#software-and-language-evolution),
|
||||
and so will need mechanisms to support evolution when using generics. New
|
||||
additions to an interface might:
|
||||
and so will need mechanisms to support evolution when using checked generics.
|
||||
New additions to an interface might:
|
||||
|
||||
- need default implementations
|
||||
- be marked "upcoming" to allow for a period of transition
|
||||
@@ -607,47 +611,39 @@ to work from C++, and Carbon functions could use that interface to invoke `swap`
|
||||
on C++ types.
|
||||
|
||||
Similarly, we will want some way to implement Carbon interfaces for C++ types.
|
||||
For example, we might have a template implementation of an `Addable` interface
|
||||
For example, we might have a template implementation of an `AddWith` interface
|
||||
for any C++ type that implements `operator+`.
|
||||
|
||||
## What we are not doing
|
||||
## What we are not doing with checked generics
|
||||
|
||||
What are we **not** doing with generics, particularly things that some other
|
||||
languages do?
|
||||
What are we **not** doing with checked generics, particularly things that some
|
||||
other languages do?
|
||||
|
||||
### Not the full flexibility of templates
|
||||
|
||||
Generics don't need to provide full flexibility of C++ templates:
|
||||
Checked generics don't need to provide full flexibility of C++ templates:
|
||||
|
||||
- The current assumption is that
|
||||
[Carbon templates](#relationship-to-templates) will cover those cases that
|
||||
don't fit inside generics, such as code that relies on compile-time duck
|
||||
typing.
|
||||
- [Carbon templates](#templates) will cover those cases that don't fit inside
|
||||
checked generics, such as code that relies on compile-time duck typing.
|
||||
- We won't allow a specialization of some generic interface for some
|
||||
particular type to actually expose a _different_ interface, with different
|
||||
methods or different types in method signatures. This would break modular
|
||||
type checking.
|
||||
- [Template metaprogramming](https://en.wikipedia.org/wiki/Template_metaprogramming)
|
||||
will not be supported by Carbon generics. We expect to address those use
|
||||
cases with metaprogramming or templates in Carbon.
|
||||
will not be supported by Carbon checked generics. We expect to address those
|
||||
use cases with metaprogramming or [templates](#templates) in Carbon.
|
||||
- [Expression templates](https://en.wikipedia.org/wiki/Expression_templates)
|
||||
are out of scope. It may be possible to express this approach in Carbon's
|
||||
checked-generics system, but they won't drive any accommodation in the
|
||||
checked-generics design.
|
||||
|
||||
### Template use cases that are out of scope
|
||||
|
||||
We will also not require Carbon generics to support
|
||||
[expression templates](https://en.wikipedia.org/wiki/Expression_templates),
|
||||
[variadics](https://en.wikipedia.org/wiki/Variadic_function), or
|
||||
[variadic templates](https://en.wikipedia.org/wiki/Variadic_template). Those are
|
||||
all out of scope. It would be fine for our generics system to support these
|
||||
features, but they won't drive any accommodation in the generics design, at
|
||||
least until we have some resolution about templates in Carbon.
|
||||
|
||||
### Generics will be checked when defined
|
||||
### Checked generics will be checked when defined
|
||||
|
||||
C++ compilers must defer full type checking of templates until they are
|
||||
instantiated by the user. Carbon will not defer type checking of generic
|
||||
instantiated by the user. Carbon will not defer type checking of checked-generic
|
||||
definitions.
|
||||
|
||||
### Specialization strategy
|
||||
### Implementation strategy
|
||||
|
||||
We want all generic Carbon code to support [static dispatch](#dispatch-control).
|
||||
This means we won't support unbounded type families. Unbounded type families are
|
||||
@@ -656,7 +652,7 @@ when recursion creates an infinite collection of types, such as in
|
||||
or:
|
||||
|
||||
```carbon
|
||||
fn Sort[T:! Comparable](list: List(T)) -> List(T) {
|
||||
fn Sort[T:! Ordered](list: List(T)) -> List(T) {
|
||||
if (list.size() == 1) return list;
|
||||
var chunks: List(List(T)) = FormChunks(list, sqrt(list.size()));
|
||||
chunks = chunks.ApplyToEach(Sort);
|
||||
@@ -665,15 +661,14 @@ fn Sort[T:! Comparable](list: List(T)) -> List(T) {
|
||||
}
|
||||
```
|
||||
|
||||
This, given an implementation of `Comparable` for any list with elements that
|
||||
are themselves `Comparable`, would recursively call itself to produce a set of
|
||||
types without bound. That is, calling `Sort` on a `List(Int)` would internally
|
||||
call `Sort` on a `List(List(Int))` and so on recursively without any static
|
||||
limit.
|
||||
This, given an implementation of `Ordered` for any list with elements that are
|
||||
themselves `Ordered`, would recursively call itself to produce a set of types
|
||||
without bound. That is, calling `Sort` on a `List(Int)` would internally call
|
||||
`Sort` on a `List(List(Int))` and so on recursively without any static limit.
|
||||
|
||||
We won't require all generic Carbon code to support dynamic dispatch, but we
|
||||
would like it to be an implementation option for the compiler in the majority of
|
||||
cases.
|
||||
We won't require all checked-generic Carbon code to support dynamic dispatch,
|
||||
but we would like it to be an implementation option for the compiler in the
|
||||
majority of cases.
|
||||
|
||||
Lastly, runtime specialization is out of scope as an implementation strategy.
|
||||
That is, some language runtimes JIT a specialization when it is first needed,
|
||||
@@ -681,5 +676,8 @@ but it is not a goal for Carbon to support such an implementation strategy.
|
||||
|
||||
## References
|
||||
|
||||
Proposals:
|
||||
|
||||
- [#24: Generics goals](https://github.com/carbon-language/carbon-lang/pull/24)
|
||||
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
|
||||
- [#2138: Checked and template generic terminology](https://github.com/carbon-language/carbon-lang/pull/2138)
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ Interfaces are Carbon's
|
||||
[only static open extension mechanism](/docs/project/principles/static_open_extension.md),
|
||||
so user-defined indexing must be defined in terms of interfaces. In order to
|
||||
support
|
||||
[definition checking of generic functions](/docs/design/generics/goals.md#generics-will-be-checked-when-defined),
|
||||
[definition checking of generic functions](/docs/design/generics/goals.md#checked-generics-will-be-checked-when-defined),
|
||||
it must be possible to fully typecheck indexing operations using only the
|
||||
information exposed by those interfaces. And in order to provide
|
||||
[coherence](/docs/design/generics/goals.md#coherence), a given indexing
|
||||
|
||||
Reference in New Issue
Block a user