mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:50:14 +01:00
Consistent class and interface syntax (#2760)
Update syntax of `class` and `interface` definitions to be more consistent. Constructs that add names to the class or interface from another definition are always prefixed by the `extend` keyword. Implements the decisions in: - [#995: Generics external impl versus extends](https://github.com/carbon-language/carbon-lang/issues/995), - [#1159: adaptor versus adapter may be harder to spell than we'd like](https://github.com/carbon-language/carbon-lang/issues/1159), - [#2580: How should Carbon handle conditionally implemented internal interfaces](https://github.com/carbon-language/carbon-lang/issues/2580), and - [#2770: Terminology for internal and external implementations](https://github.com/carbon-language/carbon-lang/issues/2770). Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
@@ -37,6 +37,7 @@ in Unicode Normalization Form C (NFC).
|
||||
The following words are interpreted as keywords:
|
||||
|
||||
- `abstract`
|
||||
- `adapt`
|
||||
- `addr`
|
||||
- `alias`
|
||||
- `and`
|
||||
@@ -51,8 +52,7 @@ The following words are interpreted as keywords:
|
||||
- `continue`
|
||||
- `default`
|
||||
- `else`
|
||||
- `extends`
|
||||
- `external`
|
||||
- `extend`
|
||||
- `final`
|
||||
- `fn`
|
||||
- `for`
|
||||
@@ -77,6 +77,7 @@ The following words are interpreted as keywords:
|
||||
- `partial`
|
||||
- `private`
|
||||
- `protected`
|
||||
- `require`
|
||||
- `return`
|
||||
- `returned`
|
||||
- `Self`
|
||||
|
||||
@@ -0,0 +1,838 @@
|
||||
# Consistent `class` and `interface` syntax
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
[Pull request](https://github.com/carbon-language/carbon-lang/pull/2760)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Problem](#problem)
|
||||
- [Background](#background)
|
||||
- [Proposal](#proposal)
|
||||
- [Details](#details)
|
||||
- [Class inheritance](#class-inheritance)
|
||||
- [Class implementing an interface](#class-implementing-an-interface)
|
||||
- [Class conditional implementation](#class-conditional-implementation)
|
||||
- [Adapters](#adapters)
|
||||
- [Interfaces](#interfaces)
|
||||
- [Future work: mixins](#future-work-mixins)
|
||||
- [Rationale](#rationale)
|
||||
- [Alternatives considered](#alternatives-considered)
|
||||
- [Use `extends` instead of `extend`](#use-extends-instead-of-extend)
|
||||
- [Allow interfaces to `require` another interface without writing `Self impls`](#allow-interfaces-to-require-another-interface-without-writing-self-impls)
|
||||
- [Allow other kinds of `where` clauses after `require`](#allow-other-kinds-of-where-clauses-after-require)
|
||||
- [Continue to use `impl as` for interface requirements](#continue-to-use-impl-as-for-interface-requirements)
|
||||
- [Continue to use `adapter` or `adaptor` instead of `adapt`](#continue-to-use-adapter-or-adaptor-instead-of-adapt)
|
||||
- [Use some other syntax for extending adapters](#use-some-other-syntax-for-extending-adapters)
|
||||
- [Continue to have some term for "external" or non-extended implementations](#continue-to-have-some-term-for-external-or-non-extended-implementations)
|
||||
- [More direct support for conditionally implementing internal interfaces](#more-direct-support-for-conditionally-implementing-internal-interfaces)
|
||||
- [Use `external` consistently instead of `extend`](#use-external-consistently-instead-of-extend)
|
||||
- [Use `mix` keyword for mixins](#use-mix-keyword-for-mixins)
|
||||
- [Allow more control over access to mixins](#allow-more-control-over-access-to-mixins)
|
||||
- [List base class in class declaration](#list-base-class-in-class-declaration)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Abstract
|
||||
|
||||
Update syntax of `class` and `interface` definitions to be more consistent.
|
||||
Constructs that add names to the class or interface from another definition are
|
||||
always prefixed by the `extend` keyword.
|
||||
|
||||
Implements the decisions in:
|
||||
|
||||
- [#995: Generics external impl versus extends](https://github.com/carbon-language/carbon-lang/issues/995),
|
||||
- [#1159: adaptor versus adapter may be harder to spell than we'd like](https://github.com/carbon-language/carbon-lang/issues/1159),
|
||||
- [#2580: How should Carbon handle conditionally implemented internal interfaces](https://github.com/carbon-language/carbon-lang/issues/2580),
|
||||
and
|
||||
- [#2770: Terminology for internal and external implementations](https://github.com/carbon-language/carbon-lang/issues/2770).
|
||||
|
||||
## Problem
|
||||
|
||||
Classes and adapters, prior to this proposal, use `impl` to say that an
|
||||
interface is
|
||||
[implemented internally](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/terminology.md#internal-impl),
|
||||
which means that the names that are members of the interface are included as
|
||||
names of the class. The keyword `external` is added to indicate the names should
|
||||
not be included. Interfaces and named constraints, in contrast, use `impl` to
|
||||
mean another interface is required, but its names are not included. Instead, to
|
||||
include the names, the `extends` keyword used instead of `impl`.
|
||||
|
||||
| Include names: | Yes | No |
|
||||
| ------------------------- | --------- | --------------- |
|
||||
| `class`, `adapter` | `impl` | `external impl` |
|
||||
| `interface`, `constraint` | `extends` | `impl` |
|
||||
|
||||
In the time since this syntax has been introduced, we have found `external` in
|
||||
particular easy to accidentally omit.
|
||||
|
||||
In addition to resolving this inconsistency, it would be an advantage if readers
|
||||
of a class could quickly scan the definition to identify other places to look
|
||||
for members that contribute to the class' API.
|
||||
|
||||
## Background
|
||||
|
||||
These proposals that defined the syntax for these entities that are being
|
||||
modified:
|
||||
|
||||
- [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553)
|
||||
defined the syntax for classes implementing interfaces, internally or
|
||||
externally, and the syntax for named constraints (then "structural
|
||||
interfaces") and interfaces requiring or extending other interfaces.
|
||||
- [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
|
||||
defined the syntax for adapters and extending adapters.
|
||||
- [#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084)
|
||||
allowed forward declaration of implementations, so internal `impl`
|
||||
declarations may appear outside of a class definition, and `external impl`
|
||||
declarations may appear inside.
|
||||
- [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777)
|
||||
defined the syntax for a class to extend a base class.
|
||||
|
||||
No proposal so far has defined how forward declarations work for classes. The
|
||||
rule used for forward `interface`, `constraint`, and `impl` declarations is that
|
||||
the declaration part of the definition is everything up to the opening `{` of
|
||||
the definition body. See
|
||||
[the forward declaration section of the Generics details design doc](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-declarations-and-cyclic-references)
|
||||
added in proposal
|
||||
[#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084).
|
||||
|
||||
This proposal incorporates the decisions made in these question-for-leads
|
||||
issues:
|
||||
|
||||
- [#995: Generics external impl versus extends](https://github.com/carbon-language/carbon-lang/issues/995),
|
||||
- [#1159: adaptor versus adapter may be harder to spell than we'd like](https://github.com/carbon-language/carbon-lang/issues/1159),
|
||||
- [#2580: How should Carbon handle conditionally implemented internal interfaces](https://github.com/carbon-language/carbon-lang/issues/2580),
|
||||
and
|
||||
- [#2770: Terminology for internal and external implementations](https://github.com/carbon-language/carbon-lang/issues/2770).
|
||||
|
||||
Some of thinking around the resolution of #995 was documented in
|
||||
[issue #2293: reconsider syntax for internal / external implementation of interfaces](https://github.com/carbon-language/carbon-lang/issues/2293),
|
||||
which was closed as a duplicate of #995.
|
||||
|
||||
In addition to modifying syntax from previous proposals,
|
||||
[#995](https://github.com/carbon-language/carbon-lang/issues/995) also gives a
|
||||
syntax for using a mixin in a class. Mixins are described as a use case in
|
||||
[#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561),
|
||||
but have not been added in any proposal. Question-for-leads issue
|
||||
[#1000: Mixins: base classes or data members?](https://github.com/carbon-language/carbon-lang/issues/1000),
|
||||
does state that a class will treat a mixin syntactically like a data member
|
||||
instead of a base class.
|
||||
|
||||
## Proposal
|
||||
|
||||
Any declaration that adds the names from another entity shall start with the
|
||||
(new) `extend` keyword. This includes:
|
||||
|
||||
- _Inheritance_: A class now indicates that it inherits from a base class
|
||||
using an
|
||||
|
||||
> `extend base:` _base-class_ `;`
|
||||
|
||||
declaration inside the class definition. The `extend` keyword indicates that
|
||||
the API of the base class is included.
|
||||
|
||||
- _Adapters_: Adapter types are now declared as a class, with an
|
||||
|
||||
> [ `extend` ] `adapt` _adapted-class_ `;`
|
||||
|
||||
declaration inside the definition, as an alternative to a base class
|
||||
declaration. The optional `extend` keyword controls whether the API of the
|
||||
adapted class is included.
|
||||
|
||||
- _Implementations_: Internal implementations are marked with the `extend`
|
||||
keyword on the declaration inside the class. Only the declaration inside the
|
||||
class, which is required for internal implementations, uses the `extend`
|
||||
keyword. External implementations are not marked.
|
||||
|
||||
> [ `extend` ] `impl` ...
|
||||
|
||||
- _Interfaces_: The `extends` declaration in an interface definition is
|
||||
replaced by an `extend` declaration, with no change except removing the `s`
|
||||
from the end of the keyword. Other interface requirements are now written
|
||||
using a `require` declaration, with a constraint that matches a `where`
|
||||
clause. This means
|
||||
|
||||
> `impl as` _required-interface_ `;`
|
||||
|
||||
will now be written as
|
||||
|
||||
> `require Self impls` _required-interface_ `;`
|
||||
|
||||
and
|
||||
|
||||
> `impl` _type-expression_ `as` _required-interface_ `;`
|
||||
|
||||
will now be written as
|
||||
|
||||
> `require` _type-expression_ `impls` _required-interface_ `;`
|
||||
|
||||
For now, only the `impls` forms of `where` clauses are permitted after
|
||||
`require`.
|
||||
|
||||
In summary:
|
||||
|
||||
| Before | After |
|
||||
| ------------------------------- | --------------------------------------- |
|
||||
| `class D extends B { ... }` | `class D { extend base: B; ... }` |
|
||||
| `external impl C as Sub;` | `impl C as Sub;` |
|
||||
| `class C { impl as Sortable; }` | `class C { extend impl as Sortable; }` |
|
||||
| `adapter A for C { ... }` | `class A { adapt C; ... }` |
|
||||
| `adapter A extends C { ... }` | `class A { extend adapt C; ... }` |
|
||||
| `interface I { impl as J; }` | `interface I { require Self impls J; }` |
|
||||
| `interface I { impl T as J; }` | `interface I { require T impls J; }` |
|
||||
| `interface I { extends J; }` | `interface I { extend J; }` |
|
||||
|
||||
None of `adapter`, `extends`, `external` will continue to be keywords. To match
|
||||
these changes, "internal implementations" will now be referred to as "extended
|
||||
implementations," and we will no longer use "external" to refer to
|
||||
implementations.
|
||||
|
||||
In addition, we drop the syntax for conditionally implemented internal
|
||||
interfaces. Instead, an external interface implementation can be combined with
|
||||
aliases to the members of the interface.
|
||||
|
||||
## Details
|
||||
|
||||
### Class inheritance
|
||||
|
||||
What was previously written:
|
||||
|
||||
```
|
||||
base class B;
|
||||
class D extends B;
|
||||
class D extends B {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
is now written:
|
||||
|
||||
```
|
||||
base class B;
|
||||
class D;
|
||||
class D {
|
||||
extend base: B;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
An extend base class declaration may appear in the body of a class definition,
|
||||
and has this form:
|
||||
|
||||
> `extend` `base` `:` _type-expression_ `;`
|
||||
|
||||
The `extend base: B;` declaration must appear before any other data member
|
||||
declaration, including any [mixin declaration](#future-work-mixins), once those
|
||||
are added. This reflects both the importance of the information, and the fact
|
||||
that the base subobject appears first in the memory layout of objects.
|
||||
|
||||
Note that `base` is already a keyword, for example used in `base class`
|
||||
declarations. The colon in `base: B` is to indicate that `base` acts like a data
|
||||
member for
|
||||
[purposes of initialization](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#constructors).
|
||||
|
||||
This makes the part of a class definition that is used in forward declarations
|
||||
be exactly the part before the curly braces (`{`...`}`). Before this proposal,
|
||||
class forward declarations would exclude the `extends` clause from the the first
|
||||
line of the class definition. This change makes classes consistent with other
|
||||
entities that may be forward declared.
|
||||
|
||||
### Class implementing an interface
|
||||
|
||||
What was previously written:
|
||||
|
||||
```
|
||||
interface Sortable;
|
||||
interface Add;
|
||||
interface Sub;
|
||||
class C;
|
||||
|
||||
// Forward declaration says whether external.
|
||||
impl C as Sortable;
|
||||
external impl C as Add;
|
||||
|
||||
class C {
|
||||
// Internal impl contributes to the API.
|
||||
impl as Sortable;
|
||||
|
||||
// External impl of an operator.
|
||||
external impl as Add;
|
||||
}
|
||||
|
||||
// External impl of an operator.
|
||||
external impl C as Sub;
|
||||
|
||||
// Definition of `impl` declared earlier.
|
||||
impl C as Sortable { ... }
|
||||
external impl C as Add { ... }
|
||||
```
|
||||
|
||||
is now written:
|
||||
|
||||
```
|
||||
interface Sortable;
|
||||
interface Add;
|
||||
interface Sub;
|
||||
class C;
|
||||
|
||||
// Forward declaration same whether extended or not.
|
||||
impl C as Sortable;
|
||||
impl C as Add;
|
||||
|
||||
class C {
|
||||
// Extended impl contributes to the API.
|
||||
extend impl as Sortable;
|
||||
|
||||
// (Non-extended) Impl of an operator.
|
||||
impl as Add;
|
||||
}
|
||||
|
||||
// (Non-extended) Impl of an operator.
|
||||
impl C as Sub;
|
||||
|
||||
// Definition of `impl` declared earlier.
|
||||
impl C as Sortable { ... }
|
||||
impl C as Add { ... }
|
||||
```
|
||||
|
||||
Whether an interface is extended or not is now only reflected in its declaration
|
||||
inside the class body, not in any declaration or definition outside.
|
||||
|
||||
An `impl` declaration, with this proposal, must have one of these two forms:
|
||||
|
||||
- Without an `extend` keyword prefix, used for non-extended `impl`
|
||||
declarations and for all `impl` declarations outside of a class body:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> `impl` [`forall` `[` _deduced-parameters_ `]`] [_type-expression_] `as`
|
||||
> _facet-type-expression_ (`;`|`{` _impl-body_ `}`)
|
||||
|
||||
The _type-expression_ is required outside of a class body, otherwise it
|
||||
defaults to `Self`.
|
||||
|
||||
- With an `extend` keyword prefix, to indicate this implementation is
|
||||
extended, only in a class body:
|
||||
|
||||
> `extend` `impl` `as` _facet-type-expression_ (`;`|`{` _impl-body_ `}`)
|
||||
|
||||
Note that this form does not allow either a `forall` clause nor a
|
||||
_type-expression_ before the `as` keyword. This reflects the restriction
|
||||
that
|
||||
[wildcard `impl` declarations must never be extended (formerly: "always be external")](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#wildcard-impl-declarations),
|
||||
and that this proposal removes support for
|
||||
[extended (formerly "internal") conditional implementation](#class-conditional-implementation).
|
||||
|
||||
### Class conditional implementation
|
||||
|
||||
We remove direct support for conditionally implemented extended (formerly
|
||||
"internal") interfaces, called
|
||||
[conditional conformance](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#conditional-conformance).
|
||||
We can work around this restriction by using an non-extended interface
|
||||
implementation and aliases to the members of the interface.
|
||||
|
||||
What was previously written:
|
||||
|
||||
```
|
||||
interface Printable {
|
||||
fn Print[self: Self]();
|
||||
}
|
||||
|
||||
class Vector(T:! type) {
|
||||
// ...
|
||||
|
||||
impl forall [U:! Printable] Vector(U) as Printable {
|
||||
fn Print[self: Self]();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
is now written:
|
||||
|
||||
```
|
||||
interface Printable {
|
||||
fn Print[self: Self]();
|
||||
}
|
||||
|
||||
class Vector(T:! type) {
|
||||
// ...
|
||||
alias Print = Printable.Print;
|
||||
}
|
||||
|
||||
impl forall [U:! Printable] Vector(U) as Printable {
|
||||
fn Print[self: Self]();
|
||||
}
|
||||
```
|
||||
|
||||
The way this works is that `Vector.Print` is equivalent to
|
||||
`Vector.(Printable.Print)`, which may or may not be defined. The name
|
||||
`Vector.Print` can no longer be conditional, and the meaning of that name is
|
||||
fixed. However, the implementation of `Printable` for `Vector(T)` may not exist
|
||||
for some types `T`.
|
||||
|
||||
### Adapters
|
||||
|
||||
What was previously written:
|
||||
|
||||
```
|
||||
class C;
|
||||
// Forward declarations of adapters.
|
||||
adapter A for C;
|
||||
adapter E extends C;
|
||||
|
||||
// Definitions of an adapter.
|
||||
adapter A for C {
|
||||
...
|
||||
}
|
||||
// Definition of an extending adapter.
|
||||
adapter E extends C {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
is now written:
|
||||
|
||||
```
|
||||
class C;
|
||||
// Forward declarations of adapters.
|
||||
class A;
|
||||
class E;
|
||||
|
||||
// Definitions of an adapter.
|
||||
class A {
|
||||
adapt C;
|
||||
...
|
||||
}
|
||||
// Definition of an extending adapter.
|
||||
class E {
|
||||
extend adapt C;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Adapters are now a special case of classes, not a distinct top-level
|
||||
declaration.
|
||||
- Classes with `adapt` still must not contain anything that was previously
|
||||
forbidden for adapters: no fields, no base class, no virtual methods, no
|
||||
implementations of virtual methods, and so on.
|
||||
- The `adapt` declaration must appear before
|
||||
[mixin declarations](#future-work-mixins), if any.
|
||||
- The syntax for an `adapt` declaration inside a class body is:
|
||||
|
||||
> [`extend`] `adapt` _type-expression_ `;`
|
||||
|
||||
### Interfaces
|
||||
|
||||
What was previously written:
|
||||
|
||||
```
|
||||
interface A { let T:! Type; }
|
||||
interface B { let U:! Type; }
|
||||
interface C(V:! Type) { }
|
||||
|
||||
interface I {
|
||||
// `A`'s interface is incorporated into `I`:
|
||||
extends A where .T = i32;
|
||||
|
||||
// No impact on `I`s interface, but an
|
||||
// implementation must exist:
|
||||
impl as B where .U = i32;
|
||||
|
||||
// Implementation must exist on another type:
|
||||
impl i32 as C(Self);
|
||||
}
|
||||
```
|
||||
|
||||
is now written:
|
||||
|
||||
```
|
||||
interface A { let T:! Type; }
|
||||
interface B { let U:! Type; }
|
||||
interface C(V:! Type) { }
|
||||
|
||||
interface I {
|
||||
// `A`'s interface is incorporated into `I`:
|
||||
extend A where .T = i32;
|
||||
|
||||
// No impact on `I`s interface, but an
|
||||
// implementation must exist:
|
||||
require Self impls B where .U = i32;
|
||||
|
||||
// Implementation must exist on another type:
|
||||
require i32 impls C(Self);
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- The same change applies to named constraints, and is intended to be used in
|
||||
the future for named predicates used as template constraints.
|
||||
- One syntax for constraints in either `where` clauses or `require`
|
||||
declarations.
|
||||
- Want to open up the syntax to expressing more general constraints.
|
||||
- Syntax for a `require` declaration in an interface or named constraint:
|
||||
|
||||
> `require` _type-expression_ `impls` _facet-type-expression_ `;`
|
||||
|
||||
As
|
||||
[with `impl`...`as` declarations before](/docs/design/generics/details.md#interface-requiring-other-interfaces-revisited),
|
||||
a `require` declaration must use `Self`, either to the left or right of
|
||||
`impls`. Note that `require` only supports this subset of `where` clause
|
||||
expressions. Adding other kinds of constraints is future work.
|
||||
|
||||
- Syntax for an `extend` declaration in an interface or named constraint:
|
||||
|
||||
> `extend` _facet-type-expression_ `;`
|
||||
|
||||
## Future work: mixins
|
||||
|
||||
Mixins have not been defined in a proposal so far. However, part of the process
|
||||
of resolving
|
||||
[issue #995](https://github.com/carbon-language/carbon-lang/issues/995) was
|
||||
deciding on a syntax for including a mixin in a class. This was done in order to
|
||||
make sure that class declarations that included names from another entity were
|
||||
treated consistently, for example always starting with the `extend` keyword.
|
||||
|
||||
```
|
||||
// Mixin declarations and definitions are
|
||||
// outside the scope of this proposal.
|
||||
mixin M1;
|
||||
mixin M2;
|
||||
|
||||
class C {
|
||||
// Mixing in mixin M1
|
||||
extend m: M1;
|
||||
|
||||
// Mixing in mixin M2. This member is not named.
|
||||
// Initialized using `M2.MakeDefault()`.
|
||||
extend _: M2 = M2.MakeDefault();
|
||||
|
||||
// Alternative to the above `M2` that uses a
|
||||
// private name instead of no name:
|
||||
extend private m2: M2;
|
||||
}
|
||||
```
|
||||
|
||||
The declaration that a class uses a mixin is called a "mix" declaration. The
|
||||
syntax of a mix declaration is:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> `extend` [`private`|`protected`] (`_`|_id_) `:` _mixin-expression_ [`=`
|
||||
> _initializer-expression_] `;`
|
||||
|
||||
The _id_ part of the mix declaration defines the name assigned to that mixin
|
||||
subobject. This name is may be used to access members of the mixin and to
|
||||
initialize the mixin in a constructor for the class. The optional `private` or
|
||||
`protected` access specifier controls the access to this name.
|
||||
|
||||
With this proposal, base class declarations appear in the body of the class
|
||||
definition, like data members, so decision of whether mixins are more like base
|
||||
classes or data members of issue
|
||||
[#1000: Mixins: base classes or data members?](https://github.com/carbon-language/carbon-lang/issues/1000)
|
||||
is less significant. Like base classes, the mix declaration syntax begins with
|
||||
`extend`. Like data members, a class may have multiple mix declarations and they
|
||||
may be intermixed with field declarations. The layout of the memory of an object
|
||||
reflects the order of the declarations in the class body, defining the order of
|
||||
the mixin and field subobjects.
|
||||
|
||||
## Rationale
|
||||
|
||||
The main reason for the new syntax is consistency and simplification:
|
||||
|
||||
- The use of the `extend` keyword is the consistent way to mark what other
|
||||
entities are consulted during name lookup.
|
||||
- The `class` declaration is simplified by moving more into the definition
|
||||
body.
|
||||
- Making adapters a kind of class removes a kind of top-level declaration, a
|
||||
simplification, and matches how base classes are declared, a consistency.
|
||||
- Dropping the class conditional implementation is a simplification.
|
||||
|
||||
These consistency and simplification improvements help:
|
||||
|
||||
- ease the implementation of
|
||||
[language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
|
||||
by reducing the size of the language, and providing regularities
|
||||
implementations can use to reuse code or more easily identify relevant parts
|
||||
of the code for queries;
|
||||
- make Carbon code
|
||||
[easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
|
||||
- teaching the language, since there is less to learn.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### Use `extends` instead of `extend`
|
||||
|
||||
Keyword `extend` was chosen over `extends` to parallel `impl`, a declaration,
|
||||
instead of `impls`, a binary predicate, decided in
|
||||
[issue #2495](https://github.com/carbon-language/carbon-lang/issues/2495) and
|
||||
accepted in
|
||||
[proposal #2483](https://github.com/carbon-language/carbon-lang/pull/2483).
|
||||
|
||||
We chose to use `require` instead of `requires` and `adapt` instead of `adapts`
|
||||
for the same consistency.
|
||||
|
||||
### Allow interfaces to `require` another interface without writing `Self impls`
|
||||
|
||||
We
|
||||
[considered](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1418387971)
|
||||
allowing `interface I { require J; }` as a short-hand for
|
||||
`interface I { require Self impls J; }`. This is something we would consider
|
||||
adding in the future based on experience with the current approach, but for now
|
||||
we wanted to maintain consistency with the constraint syntax of `where` clauses.
|
||||
|
||||
This decision and rationale was described in
|
||||
[this comment in #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1416659207).
|
||||
|
||||
### Allow other kinds of `where` clauses after `require`
|
||||
|
||||
The decision on
|
||||
[#995](https://github.com/carbon-language/carbon-lang/issues/995), see
|
||||
[1](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1439336141)
|
||||
and
|
||||
[2](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1496781087),
|
||||
called for the same constraint syntax after `require` as we are using after
|
||||
`where`. This proposal _only_ allows `impls` clauses after `require`, since
|
||||
there were concerns about the other kinds of clauses:
|
||||
|
||||
- We had questions about what syntax to use to refer to members of the
|
||||
interface, such as associated types, to constrain them. Must they start with
|
||||
`Self.` or `.`? See
|
||||
[this comment thread on #2760](https://github.com/carbon-language/carbon-lang/pull/2760#discussion_r1213789364).
|
||||
- Modifying the constraints on an associated type after the declaration for
|
||||
that associated type was not something we clearly wanted to allow. Allowing
|
||||
that would mean having to read the whole interface definition body to
|
||||
understand a single member.
|
||||
- It was unclear if rewrite constraints (using `=` instead of `==`), would be
|
||||
allowed in a `require` declaration, and if they were, whether they would be
|
||||
changed into equality constraints (as if they were declared using `==`). See
|
||||
[this comment on #2760](https://github.com/carbon-language/carbon-lang/pull/2760#discussion_r1213791304).
|
||||
- It would have provided more different ways of declaring equivalent
|
||||
interfaces. This concern was raised
|
||||
[in this comment on #2760](https://github.com/carbon-language/carbon-lang/pull/2760#discussion_r1214571215).
|
||||
|
||||
For now, we only needed to replace the existing uses of `impl as` constraints,
|
||||
which had none of these concerns. We did not want to block this proposal, so we
|
||||
made sure the `require` clauses were consistent with that _subset_ of `where`
|
||||
clauses.
|
||||
|
||||
### Continue to use `impl as` for interface requirements
|
||||
|
||||
There were a few reasons motivating the change to use the new `require`
|
||||
declarations in interfaces and named constraints, instead of using `impl as` to
|
||||
match how a type could satisfy that requirement. These mostly came down to some
|
||||
observed breaks in the parallel structure between the requirement in interfaces
|
||||
and the satisfaction of that requirement in types.
|
||||
|
||||
- Whether an interface `I` extends or just requires another interface `J` is
|
||||
independent of whether a type implementing `I` extends or just implements
|
||||
`J`.
|
||||
- If `R` requires interface `I`, the implementation of `R` for a type won't
|
||||
have the implementation of `I` as a nested sub-block.
|
||||
- With the change in
|
||||
[#2173: Associated constant assignment versus equality](https://github.com/carbon-language/carbon-lang/pull/2173),
|
||||
the behavior of `impl as` in interfaces is different from in classes with
|
||||
respect to rewrites of associated types, motivating a change to make those
|
||||
look more different.
|
||||
|
||||
Furthermore, we had a desire to be able to express the full range of constraints
|
||||
in `where` clauses in named constraints, and we wanted the transformation from a
|
||||
`where` clause to a named constraint to be straightforward. We also wanted the
|
||||
syntax for constraints to be the same between interfaces and named constraints,
|
||||
at least for all constraints that were allowed in both.
|
||||
|
||||
This was discussed in
|
||||
[#generics-and-templates on 2023-01-30](https://discord.com/channels/655572317891461132/941071822756143115/1069691751968817283)
|
||||
and in
|
||||
[issue #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1412478644).
|
||||
|
||||
### Continue to use `adapter` or `adaptor` instead of `adapt`
|
||||
|
||||
We didn't want to allow both `adapter` and `adaptor`, since that adds complexity
|
||||
for readers and tooling, but neither seemed clearly dominant enough in usage to
|
||||
pick one over the other. By moving the declaration into the body of the class
|
||||
definition, we were able to switch from the noun form to the verb form of
|
||||
`adapt`, which doesn't have an alternate form in common usage. See
|
||||
[#1159: adaptor versus adapter may be harder to spell than we'd like](https://github.com/carbon-language/carbon-lang/issues/1159)
|
||||
for the discussion.
|
||||
|
||||
We also considered using `adapts` in a class declaration, as in
|
||||
`class PlayableSong adapts Song { ... }`, see
|
||||
[this comment in #1159](https://github.com/carbon-language/carbon-lang/issues/1159#issuecomment-1316669416).
|
||||
This would have also worked, but was not consistent with our resolution of
|
||||
[#995: Generics external impl versus extends](https://github.com/carbon-language/carbon-lang/issues/995).
|
||||
|
||||
### Use some other syntax for extending adapters
|
||||
|
||||
In the
|
||||
[open discussion on 2023-02-27](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.9steyq834zuq),
|
||||
we discussed some alternatives to `extend adapt` _adapted-class_ `;`:
|
||||
|
||||
- Adding `and` to make it read more like fluent English:
|
||||
|
||||
> `extend and adapt` _adapted-class_ `;`
|
||||
|
||||
However, this felt arbitrary and not compositional.
|
||||
|
||||
- Make the extending and adapting be separate declarations:
|
||||
|
||||
> `extend` _adapted-class_ `;` <br> `adapt` _adapted-class_ `;`
|
||||
|
||||
This felt too repetitive.
|
||||
|
||||
- Make the only way to make an extending adapter be trying to inherit from a
|
||||
final base class
|
||||
|
||||
> `extend base:` _adapted-class_ `;`
|
||||
|
||||
However, this meant using the same syntax for two different things that
|
||||
could only be distinguished by looking at the declaration of the adapted
|
||||
class, which could be far away. It also would have meant making an extending
|
||||
adapter of a non-final class much more cumbersome.
|
||||
|
||||
Ultimately, we decided that `extend adapt` would be the most compositional way
|
||||
of combining `extend` and `adapt`, so that is what users would expect. Reading
|
||||
like natural English was not considered essential.
|
||||
|
||||
### Continue to have some term for "external" or non-extended implementations
|
||||
|
||||
The fact that there were some different rules for external implementations was
|
||||
brought up in
|
||||
[this post in #2770](https://github.com/carbon-language/carbon-lang/issues/2770#issuecomment-1509288478).
|
||||
However,
|
||||
[this reply](https://github.com/carbon-language/carbon-lang/issues/2770#issuecomment-1515522070)
|
||||
pointed out those rules could be clearly stated in terms of where you are
|
||||
allowed to write `extend`. That same post made the convincing argument that to
|
||||
get the maximum benefit of the decision on
|
||||
[#995](https://github.com/carbon-language/carbon-lang/issues/995), we should
|
||||
treat `extend` and `impl` as separate orthogonal concepts as much as possible.
|
||||
|
||||
### More direct support for conditionally implementing internal interfaces
|
||||
|
||||
We considered two different ways of more directly supporting conditionally
|
||||
extending a class by an interface:
|
||||
|
||||
- Prior to this proposal, both name lookup and the implementation were
|
||||
conditional on the same condition. This has the disadvantage of entangling
|
||||
the concerns of name lookup and implementation, and was considered a complex
|
||||
and difficult model.
|
||||
- We also considered fully separating these features, and allowing a type to
|
||||
separately extend its API with an interface and then only conditionally
|
||||
implement that interface. This would allow name lookup to succeed
|
||||
unconditionally, but in cases where the names were in fact not implemented
|
||||
it would produce an error.
|
||||
|
||||
Both of these approaches would have required some support for expressing this in
|
||||
the new syntax. None of the syntactic approaches we considered were found to be
|
||||
satisfactory. By making name lookup never conditional, it made it much easier to
|
||||
have a consistent marker for declarations that extended name lookup.
|
||||
|
||||
Ultimately, the alternative of not having a dedicated syntax to support this
|
||||
case seemed the simplest in the short term, given the workaround of conditional
|
||||
external (non-extending) implementation paired with aliases that provide the
|
||||
name lookup, unconditionally. We can always add dedicated syntax later, given
|
||||
sufficient motivating information.
|
||||
|
||||
The options were considered in
|
||||
[#2580: How should Carbon handle conditionally implemented internal interfaces](https://github.com/carbon-language/carbon-lang/issues/2580).
|
||||
|
||||
### Use `external` consistently instead of `extend`
|
||||
|
||||
We considered making "extending name lookup" the default and overriding that
|
||||
default with the `external` keyword. The argument for this option rested on it
|
||||
being more convenient to express conditional internal implementation, and wasn't
|
||||
seen as attractive once that feature was removed. In the discussion, we
|
||||
generally preferred marking additional places to consult in name lookup since
|
||||
that was something we expected readers of the code to want to specifically look
|
||||
for.
|
||||
|
||||
This option was proposed as the third option in the
|
||||
[original #995 question](https://github.com/carbon-language/carbon-lang/issues/995#issue-1085174338),
|
||||
and was considered in
|
||||
[this comment](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1006096692).
|
||||
|
||||
### Use `mix` keyword for mixins
|
||||
|
||||
We considered a variety of different syntax options for using a mixin for a type
|
||||
in
|
||||
[this comment on #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1315948818).
|
||||
Many of them used the `mix` keyword, but we ultimately decided that `mix` was
|
||||
redundant with saying `extend`, which we wanted to be included with all
|
||||
constructs extending the type's API by another entity.
|
||||
|
||||
### Allow more control over access to mixins
|
||||
|
||||
There were three different aspects of mixins that we considered giving control
|
||||
over access:
|
||||
|
||||
- the inclusion of names exported by the mixin into the mixer class,
|
||||
- the ability to cast between the mixin and the mixer class types, and
|
||||
- the ability to use the name of the mixin given by the mixer class to access
|
||||
members of the mixin.
|
||||
|
||||
This was particularly relevant when we were considering separate declarations
|
||||
for declaring the mixin member and the API extension
|
||||
([1](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1315948818),
|
||||
[2](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1317598076)).
|
||||
|
||||
This approach had the problem that the common case for mixins was extending the
|
||||
API, which would not have been the default. The only examples we had where the
|
||||
mixer class would not want to include the names exported by the mixin were cases
|
||||
where the mixin had nothing to export. This led to
|
||||
[the position](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1325746398)
|
||||
that the mixin would control which of its member would be included into the
|
||||
mixer class' API -- that is the mixin would "inject" members rather than
|
||||
"export" them and leave it up to the mixer class to import them.
|
||||
|
||||
We had concerns that there might be name conflicts, but we thought those might
|
||||
be handled by some other mechanism. This is being considered in
|
||||
question-for-leads issue
|
||||
[#2745: Name conflicts beyond inheritance](https://github.com/carbon-language/carbon-lang/issues/2745).
|
||||
|
||||
We wanted mixin member names to behave consistently like other class member
|
||||
names, and so default to public but can have a `private` modifier to make
|
||||
private, following
|
||||
[#665: `private` vs `public` _syntax_ strategy, as well as other visibility tools like `external`/`api`/etc.](https://github.com/carbon-language/carbon-lang/issues/665).
|
||||
We decided to put the `private` keyword _between_ the `extend` keyword and the
|
||||
member name for two reasons:
|
||||
|
||||
- to make it easier to scan for all uses of `extend` in a class, and
|
||||
- to make it clearer that the `private` access control only applies to the
|
||||
member name, not what the `extend` controls.
|
||||
|
||||
We did not see a use case for controlling the ability to cast between the mixin
|
||||
and the mixer class types separately from being able to access the name the
|
||||
mixin member of the mixin class. This was consistent with our desire to limit
|
||||
declarations to a single access control specifier per declaration, see
|
||||
[this update in #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1322892195).
|
||||
|
||||
### List base class in class declaration
|
||||
|
||||
By moving the base class into the class body, we accomplished three things:
|
||||
|
||||
- made forward declarations shorter,
|
||||
- made it possible to inherit from a type declared inside the body of the
|
||||
class definition, and
|
||||
- allowed greater consistency, all API extensions are now declarations in the
|
||||
body of the class definition starting with `extend`.
|
||||
|
||||
This was decided in
|
||||
[this comment on #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1317598076).
|
||||
|
||||
This left open the question of what keyword introducer to use in base class
|
||||
declarations, since using `base` would cause an ambiguity with declaring a
|
||||
member class that could be extended, as considered in
|
||||
[this comment](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1320614313).
|
||||
Ultimately we avoided this problem by requiring base class declarations to
|
||||
always begin with `extend`, not support any form of private or protected
|
||||
inheritance (see
|
||||
[this comment](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1325746398)),
|
||||
and not support any combination of an `extend` declaration with a member class
|
||||
declaration.
|
||||
Reference in New Issue
Block a user