mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 13:51:04 +01:00
This PR corrects misspellings identified by the [check-spelling action](https://github.com/marketplace/actions/check-spelling). The misspellings have been reported at https://github.com/jsoref/carbon-lang/commit/38a1c1640151899fd6da0442a92557f9543b6280#commitcomment-79197316 The action reports that the changes in this PR would make it happy: https://github.com/jsoref/carbon-lang/commit/173c8f9083a68aa61f7cfe94f720f1e5dc7f1ea3 Note: this PR does not include the action. If you're interested in running a spell check on every PR and push, that can be offered separately. Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
499 lines
23 KiB
Markdown
499 lines
23 KiB
Markdown
# Inheritance
|
|
|
|
<!--
|
|
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/777)
|
|
|
|
<!-- toc -->
|
|
|
|
## Table of contents
|
|
|
|
- [Problem](#problem)
|
|
- [Background](#background)
|
|
- [Proposal](#proposal)
|
|
- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
|
|
- [Alternatives considered](#alternatives-considered)
|
|
- [Classes are final by default](#classes-are-final-by-default)
|
|
- [Allow keywords to be written when they would have no effect](#allow-keywords-to-be-written-when-they-would-have-no-effect)
|
|
- [Allow `final class`](#allow-final-class)
|
|
- [Allow `partial FinalClass`](#allow-partial-finalclass)
|
|
- [Different virtual override keywords](#different-virtual-override-keywords)
|
|
- [Different virtual override keyword placement](#different-virtual-override-keyword-placement)
|
|
- [Final methods](#final-methods)
|
|
- [Constructors](#constructors)
|
|
- [Different keyword than `partial`](#different-keyword-than-partial)
|
|
- [Partial facet for extensible classes](#partial-facet-for-extensible-classes)
|
|
- [Derived constructors set base fields](#derived-constructors-set-base-fields)
|
|
- [No partial facet](#no-partial-facet)
|
|
- [Only mixins and interfaces](#only-mixins-and-interfaces)
|
|
- [Swift approach](#swift-approach)
|
|
- [C# approach](#c-approach)
|
|
- [Construct function](#construct-function)
|
|
- [Implicit abstract classes](#implicit-abstract-classes)
|
|
- [No extensible objects with non-virtual destructors](#no-extensible-objects-with-non-virtual-destructors)
|
|
- [Separate "exact" and "or derived" variations on types](#separate-exact-and-or-derived-variations-on-types)
|
|
- [Separate "exact" and "or derived" variations on pointers](#separate-exact-and-or-derived-variations-on-pointers)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Problem
|
|
|
|
We would like to address the use cases for inheritance described in proposal
|
|
[#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561),
|
|
including providing a migration path for C++ types and programmers currently
|
|
using inheritance.
|
|
|
|
## Background
|
|
|
|
This is a follow up to these previous proposals defining classes:
|
|
|
|
- [#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561)
|
|
- [#722: Nominal classes and methods](https://github.com/carbon-language/carbon-lang/pull/722)
|
|
|
|
## Proposal
|
|
|
|
The proposal is to update [docs/design/classes.md](/docs/design/classes.md) as
|
|
described in [this PR](https://github.com/carbon-language/carbon-lang/pull/777).
|
|
|
|
## Rationale based on Carbon's goals
|
|
|
|
This particular proposal is focusing on these Carbon goals:
|
|
|
|
- [That code is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write),
|
|
particularly addressing mechanisms for writing object-oriented code familiar
|
|
to C++ programmers. We have attempted to include support for most C++ usage
|
|
patterns, rather than a lot of safety restrictions following the maxim that
|
|
Carbon should "focus on encouraging appropriate usage of features rather
|
|
than restricting misuse".
|
|
- [That Carbon supports writing performance-critical software](/docs/project/goals.md#performance-critical-software).
|
|
This includes removing cruft from the produced binaries by limiting support
|
|
for multiple and virtual inheritance, avoiding redundantly setting the vptr
|
|
when constructing objects, and making classes final by default.
|
|
- [That Carbon has practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms).
|
|
This proposal addresses safety concerns with non-virtual destructors by
|
|
making the unsafe case syntactically visible, and providing the tools for
|
|
safer alternative approaches by using
|
|
[final classes](<https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)#Non-subclassable_classes>).
|
|
|
|
## Alternatives considered
|
|
|
|
### Classes are final by default
|
|
|
|
This is a divergence from C++, but has a number of benefits:
|
|
|
|
- Classes are not in general safe for derivation unless they are explicitly
|
|
designed to be. One example is that a class `X` may assume that any value of
|
|
type `X*` should be treated as a pointer to exactly that type.
|
|
- Final classes are easier to evolve, since there are no concerns that a newly
|
|
added name will conflict with one in a derived class.
|
|
- It is only safe to delete a pointer to a class that is final or has a
|
|
virtual destructor. Making classes final by default means we can provide
|
|
good ergonomics without sacrificing safety.
|
|
- We want to encourage users to use composition or interfaces instead of
|
|
inheritance unless they consciously decide that is the right solution for
|
|
their use case.
|
|
- Labelling a class that supports inheritance as a `base class` puts
|
|
information about the class important to readers up front, rather than
|
|
leaving them wondering whether the class supports inheritance or just
|
|
accidentally used the default.
|
|
- The compiler can easily diagnose that a class is mistakenly final when you
|
|
attempt to extend it. It is much more difficult to determine that a class is
|
|
mistakenly a base.
|
|
- We expect there are some performance and code size benefits.
|
|
|
|
### Allow keywords to be written when they would have no effect
|
|
|
|
In both of these cases, we decided it was better that there was only one way to
|
|
write the code, than allow a keyword to be written in a situation where it only
|
|
acted as a comment without changing the meaning of the code.
|
|
|
|
#### Allow `final class`
|
|
|
|
We considered allowing `final class` as a synonym for `class` without a `base`
|
|
prefix, but we didn't feel it would provide benefit justifying the additional
|
|
complexity.
|
|
|
|
#### Allow `partial FinalClass`
|
|
|
|
We considered allowing `partial` to be used for all constructor functions. For a
|
|
final class, `partial FinalClass` would be an alias for `FinalClass`. FIXME
|
|
**Answer: No**
|
|
|
|
### Different virtual override keywords
|
|
|
|
Instead of `virtual` we considered `base`. This would create a parallel
|
|
structure between `abstract` and `base` classes on one hand, and `abstract` and
|
|
`base` methods on the other. However, we felt like this was an important case to
|
|
maintain continuity with C++.
|
|
|
|
Instead of `abstract` we considered:
|
|
|
|
- `virtual` ... `= 0`
|
|
- `required`
|
|
- `pure virtual`
|
|
- `virtual` ... `pure`
|
|
|
|
We didn't like using a suffix like `= 0` or `= pure`, since it is in place of an
|
|
implementation but we wouldn't put it out of line like an implementation. We
|
|
didn't like `= 0` despite it being consistent with C++ because it didn't reflect
|
|
the meaning in the way a keyword could, and keywords are easier to look up in
|
|
search engines. We might reconsider `required` if we decide that we want to use
|
|
that keyword in other places, such as in a mixin. In the end, we went with
|
|
`abstract` since it is used in other languages, such as Java, and could stand on
|
|
its own without having to be paired with `virtual`.
|
|
|
|
Instead of `impl` we considered using `override` as done in C++, with the
|
|
difference that the keyword would be mandatory in Carbon. There were a few
|
|
concerns with using `override`:
|
|
|
|
- It doesn't match the case where the base class doesn't have an
|
|
implementation to override because it is abstract or pure virtual.
|
|
- Concern about confusion between overriding and overloading.
|
|
|
|
The choice of `impl` is intended to draw a parallel with implementing
|
|
interfaces.
|
|
|
|
If we went with `override`, we might change the other keywords to match, using
|
|
`must_override` instead of `abstract` and `overridable` instead of `virtual`. We
|
|
might consider switching to `overridable` if we decide that is a keyword we
|
|
would use in other contexts that allow overriding without using runtime dispatch
|
|
using a virtual table, for example interfaces or mixins.
|
|
|
|
### Different virtual override keyword placement
|
|
|
|
We considered putting the virtual override keyword after the function's
|
|
signature:
|
|
|
|
```
|
|
base class MyBaseClass {
|
|
fn Overridable[me: Self]() -> i32 virtual { return 7; }
|
|
}
|
|
```
|
|
|
|
Rationale for putting the keyword to the right:
|
|
|
|
- This is less salient information than the name and signature of the
|
|
function, particularly for callers of the API.
|
|
- This choice allows the function names to line up vertically.
|
|
- This keyword is about the implementation. For example, it says whether there
|
|
is an implementation of this method in this class at all.
|
|
|
|
Unless you are extending the class, callers would not notice replacing a virtual
|
|
function with a non-virtual function calling a private virtual function.
|
|
|
|
The concern was that while this choice makes the API easier to read for users
|
|
calling methods on the base class, it makes it significantly harder to read for
|
|
users extending the base class. And extending the base class was a common enough
|
|
and important enough use case that this change was not worth also trading off
|
|
familiarity from C++.
|
|
|
|
**Reference:** This was decided in issue
|
|
[#754: Placement of the word virtual](https://github.com/carbon-language/carbon-lang/issues/754).
|
|
|
|
### Final methods
|
|
|
|
We considered allowing `final` to be used as a virtual override keyword, to mark
|
|
[non-overridable methods](<https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)#Non-overridable_methods>).
|
|
This is something we might change in the future, based on demonstrated need, but
|
|
for now we didn't see the use cases for it occurring in practice that would
|
|
justify its addition to the language. This was based on a few reasons.
|
|
|
|
- Even though this exists in C++ code, it can be dropped without changing
|
|
meaning.
|
|
- We expect you can get similar performance benefits from profile guided
|
|
optimizations and devirtualization.
|
|
- We imagined that we might use this keyword in the future with a different
|
|
meaning, such as "no shadow".
|
|
- We saw little harm in omitting this for now, even if we decide to add it in
|
|
later if and when we see that it would be useful for Carbon programmers.
|
|
|
|
Note that if we were to add final methods, they would be allowed to be
|
|
implemented in the partial facet type of a base class.
|
|
|
|
### Constructors
|
|
|
|
[Perils of Constructors](https://matklad.github.io/2019/07/16/perils-of-constructors.html)
|
|
gives a great overview of the challenges with constructors. It expresses the
|
|
advantages of the factory function approach used by Rust, but observes that
|
|
there are some difficulties making it work with inheritance and placement.
|
|
Proposal
|
|
[#257: Initialization of memory and variables](https://github.com/carbon-language/carbon-lang/pull/257/files)
|
|
addresses the placement component of construction, and this proposal extends
|
|
that approach to work with inheritance using the `partial` facet. This approach
|
|
has some benefits:
|
|
|
|
- Simplicity by not having constructors with different rules from other
|
|
functions.
|
|
- No initializer list
|
|
[shadow world](https://gbracha.blogspot.com/2014/09/a-domain-of-shadows.html).
|
|
- No distinction between constructors and other factory functions.
|
|
- No need for rules and syntax for
|
|
[delegating](https://docs.microsoft.com/en-us/cpp/cpp/delegating-constructors?view=msvc-160)
|
|
or
|
|
[convenience](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID217)
|
|
constructors.
|
|
- No need to have special handling of errors.
|
|
- Ability to write code in a derived constructor before calling its base
|
|
constructor.
|
|
- No static analysis of code, potentially with control flow, to ensure that
|
|
all fields are initialized.
|
|
|
|
We considered several alternatives, particularly in issue
|
|
[#741: Constructing an object of a derived type](https://github.com/carbon-language/carbon-lang/issues/741).
|
|
|
|
#### Different keyword than `partial`
|
|
|
|
In issue [#741](https://github.com/carbon-language/carbon-lang/issues/741), we
|
|
considered other keywords instead of `partial` to designate the facet of the
|
|
type for construction.
|
|
|
|
- `base`: Intended to indicate a
|
|
[base class subobject](https://en.cppreference.com/w/cpp/language/derived_class),
|
|
but was confusing with other uses of the word "base" to mean "the base class
|
|
of a type."
|
|
- `as_base`: Intended to address the confusion around using `base` by adding a
|
|
preposition that indicates this isn't the "base of the type." However, it
|
|
introduces confusion with the `as` operator used to cast.
|
|
- `bare`: Too far from the intended meaning.
|
|
- `impl`: This keyword is already being used for other things that are too
|
|
different from this use.
|
|
- `novirt`: Describes something about the effect of this keyword, but not why
|
|
you are using it.
|
|
- `exact`: Intended to suggest this is the is a use of the exact type, not a
|
|
possibly derived type. This, like `novirt`, was too focused on the effect of
|
|
the keyword and wasn't suggestive enough of why it was being used. Also this
|
|
didn't capture why this keyword would allow you to instantiate an abstract
|
|
base class.
|
|
- `ctor`, `construct`, `constructor`: These were the wrong part of speech. The
|
|
type is not the constructor, the function returning this type is.
|
|
- `under_construction`: Too long.
|
|
|
|
For the construction-related options, there were also concerns that we might
|
|
also use this type during destruction of an object.
|
|
|
|
#### Partial facet for extensible classes
|
|
|
|
In issue [#741](https://github.com/carbon-language/carbon-lang/issues/741), we
|
|
considered recommending using the `partial` facet in constructors of extensible
|
|
classes more strongly than the current proposal. Ultimately we decided it wasn't
|
|
necessary:
|
|
|
|
- The behavior was more consistent with C++.
|
|
- The consequences of not using `partial` are small enough, matching C++
|
|
instead of a possible improvement.
|
|
- The rule for when to use `partial` was too subtle and hard to explain.
|
|
- Ending up with a `partial` type because you declared a variable with type
|
|
`auto` seemed like a bad user experience.
|
|
- Writing both a `protected` constructor returning a `partial` type for
|
|
descendants and a public constructor returning the full type improved the
|
|
ergonomics for using the class but seemed like painful boilerplate for
|
|
authors of the class.
|
|
|
|
#### Derived constructors set base fields
|
|
|
|
In issue [#741](https://github.com/carbon-language/carbon-lang/issues/741), we
|
|
considered making the constructor of a derived class explicitly set the fields
|
|
of the base class without delegating to the base constructor, avoiding the
|
|
problem of trying to instantiate a base class that might be abstract.
|
|
|
|
It had some clear disadvantages including:
|
|
|
|
- There would be no way to make members of a base class private, particularly
|
|
their names.
|
|
- It wasn't clear how to interoperate with C++.
|
|
|
|
#### No partial facet
|
|
|
|
In issue [#741](https://github.com/carbon-language/carbon-lang/issues/741), we
|
|
considered allowing instantiating abstract base classes so they could be used to
|
|
initialize derived classes, but this was a safety regression from C++.
|
|
|
|
#### Only mixins and interfaces
|
|
|
|
In issue [#741](https://github.com/carbon-language/carbon-lang/issues/741), we
|
|
considered splitting inheritance into separate mechanisms for subtyping and
|
|
implementation reuse. Interfaces would represent APIs for subtyping purposes and
|
|
implementations would be defined in mixins. This was a major divergence from C++
|
|
and would likely cause problems for both programmers and interoperation.
|
|
|
|
#### Swift approach
|
|
|
|
[Swift initialization](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html)
|
|
requires the user to define a special `init` method that initializes the fields
|
|
of the object before calling any methods on it. For a
|
|
[derived class](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID216),
|
|
this is then followed by a call to the base's `init` method. After that is done,
|
|
there is a
|
|
[second phase of initialization](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID220)
|
|
that can operate on an object that at least has all fields initialized. This
|
|
means method calls are allowed, even though it is possible that not all
|
|
invariants of the class have been established for the object.
|
|
|
|
```
|
|
class MyClass extends BaseClass {
|
|
fn init(...) {
|
|
me.derived_field = ...;
|
|
super.init(base_arguments);
|
|
phase_2_after_fields_are_set();
|
|
}
|
|
}
|
|
```
|
|
|
|
This approach has some nice properties, for example it supports calling virtual
|
|
methods in the base class' `init` method and getting the derived implementation.
|
|
However it has some disadvantages for our purposes:
|
|
|
|
- Relies on potentially fragile static analysis of the code to determine that
|
|
all fields are initialized.
|
|
- Init methods have a number of restrictions, such as no method calls in the
|
|
first phase, to avoid potentially unsafe use of a partially initialized
|
|
value. So init methods are not ordinary method calls even though they
|
|
superficially look quite similar.
|
|
- Unclear what destructors to run if there is a failure partway through
|
|
construction.
|
|
- Would not interoperate well with C++, since derived fields are initialized
|
|
before base fields. This also means that the initial value for derived
|
|
fields can't be set using the values of the base fields set in the base's
|
|
init method.
|
|
|
|
#### C# approach
|
|
|
|
[C# Constructors](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constructors)
|
|
have names that match their class, and the constructor of a derived class starts
|
|
with a call to the base class' constructor using this `: base(...)` syntax
|
|
between the parameter list and function body:
|
|
|
|
```
|
|
class MyClass extends BaseClass {
|
|
fn MyClass(...) : base(base_arguments) {
|
|
me.derived_field = ...;
|
|
phase_2_after_fields_are_set();
|
|
}
|
|
}
|
|
```
|
|
|
|
Alternatively, a constructor can delegate to another constructor using
|
|
`: this(...)` syntax instead.
|
|
|
|
Disadvantages for our purposes:
|
|
|
|
- Doesn't allow you to write code before calling the base class' constructor.
|
|
- Constructors have a special syntax and are not ordinary functions. However
|
|
those differences would be familiar to C++ programmers.
|
|
- Relies on potentially fragile static analysis of the code to determine that
|
|
all fields are initialized and that it is safe to call methods.
|
|
- Unclear what destructors to run if there is a failure partway through
|
|
construction.
|
|
|
|
#### Construct function
|
|
|
|
We rejected prior Carbon proposal
|
|
[#98](https://github.com/carbon-language/carbon-lang/pull/98), where the user's
|
|
initialization function called a compiler-provided function to create the object
|
|
once the base constructor arguments and derived field values were known.
|
|
|
|
```
|
|
class MyClass extends BaseClass {
|
|
fn operator create(...) -> Self {
|
|
...
|
|
returned var result: Self =
|
|
construct(base_arguments, {.derived_field = ...});
|
|
phase_2_after_fields_are_set();
|
|
return result;
|
|
}
|
|
}
|
|
```
|
|
|
|
This avoids giving a name to the object being constructed until its fields have
|
|
been initialized, without relying on static analysis, making it clearer what
|
|
destructors should run in the case of failure, though the current proposal is
|
|
still clearer.
|
|
|
|
Disadvantages for our purposes:
|
|
|
|
- Complexity when initializing fields that depended on the address of the
|
|
object being constructed.
|
|
- Constructors are special, not ordinary functions.
|
|
|
|
Note that this prevents using the values assigned to the fields in the base's
|
|
constructor to determine the initial values for the derived fields. We could
|
|
address this concern by splitting the special `construct` function into two
|
|
pieces:
|
|
|
|
```
|
|
class MyClass extends BaseClass {
|
|
fn operator create(...) -> Self* {
|
|
...
|
|
var parent: BaseClass* = create_base(base_arguments);
|
|
// Can determine the values for derived fields here.
|
|
var result: Self* = construct(parent, {.derived_field = ...});
|
|
phase_2_after_fields_are_set();
|
|
return result;
|
|
}
|
|
}
|
|
```
|
|
|
|
This adds some complexity, but interoperates better with C++.
|
|
|
|
### Implicit abstract classes
|
|
|
|
We considered following C++'s approach of making classes abstract based on
|
|
having any pure virtual methods. This leads to awkward workarounds where you
|
|
might mark a class' destructor as pure virtual even though it is still
|
|
implemented. We decided to use a different introducer for abstract classes since
|
|
this is very important for readers, helping them determine the role of the class
|
|
and whether this is the class they are looking for.
|
|
|
|
We thought that if you were to change a class to being abstract, you would
|
|
likely also update its description and rename it at the same time, since that
|
|
was such an important change to the interface of the class.
|
|
|
|
### No extensible objects with non-virtual destructors
|
|
|
|
We considered forbidding constructing extensible objects with non-virtual
|
|
destructors. This was to avoid getting into a state where a type could be used
|
|
as a local variable but not allocated on the heap. It was also identified as an
|
|
advanced use case that didn't need to be as convenient to write, and so the
|
|
overhead of using both a final and an abstract type in place of an extensible
|
|
type might be more acceptable and would give much more clarity to what a given
|
|
type represented.
|
|
|
|
However, this was a noticeable divergence from C++ where extensible objects are
|
|
the default. We decided that consistency with both C++ and extensible classes
|
|
with virtual methods was more valuable. The error when deleting a base class
|
|
with a non-virtual destructor would be very clear and offer useful alternatives:
|
|
making the destructor virtual, making a final class, or using `unsafe_delete`.
|
|
This matched the idea that
|
|
[Carbon should "focus on encouraging appropriate usage of features rather than restricting misuse"](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
|
|
|
|
This topic was discussed in issue
|
|
[#652: Extensible classes with or without vtables](https://github.com/carbon-language/carbon-lang/issues/652)
|
|
and
|
|
[on Discord](https://discord.com/channels/655572317891461132/708431657849585705/880471907407888404).
|
|
|
|
### Separate "exact" and "or derived" variations on types
|
|
|
|
Issue [#652](https://github.com/carbon-language/carbon-lang/issues/652)
|
|
considered many variations on ways to have two different types for values
|
|
depending on whether they represented a value with an exact type, or a value
|
|
that could be a derived type. We ultimately decided that asking users to use
|
|
both types would be too much cognitive overhead, and would be a usability
|
|
regression from C++.
|
|
|
|
### Separate "exact" and "or derived" variations on pointers
|
|
|
|
Issue [#652](https://github.com/carbon-language/carbon-lang/issues/652)
|
|
considered instead having two kinds of pointers. One would point to a value of a
|
|
specific known type, and the other would point to a value of some derived type.
|
|
This has two disadvantages compared to having the variations be on the types of
|
|
the values.
|
|
|
|
- The distinction between pointer types is meaningless for non-extensible
|
|
types.
|
|
- We still need the distinction for value types to give the right type to the
|
|
result of dereferencing the pointer.
|