mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:20:13 +01:00
Nominal classes and methods (#722)
Add support for nominal (or "named") classes with encapsulation. Inheritance will be in a later proposal. Here is an example of the proposed syntax:
```
class Circle {
fn Create(c: Point, r: f32) -> Self {
return {.center = c, .radius = r};
}
fn Diameter[me: Self]() -> f32 {
return me.radius * 2;
}
fn Expand[addr me: Self*](distance: f32);
private var center: Point;
private var radius: f32;
}
fn Circle.Expand[addr me: Self*](distance: f32) {
me->radius += distance;
}
```
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Chandler Carruth
parent
d71f5b1784
commit
0820dec01f
@@ -4,6 +4,7 @@
|
||||
|
||||
circularly
|
||||
copyable
|
||||
crate
|
||||
inout
|
||||
pullrequest
|
||||
statics
|
||||
|
||||
+67
-8
@@ -43,8 +43,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Arrays and slices](#arrays-and-slices)
|
||||
- [User-defined types](#user-defined-types)
|
||||
- [Classes](#classes)
|
||||
- [Assignment, copying](#assignment-copying)
|
||||
- [Member access](#member-access)
|
||||
- [Methods](#methods)
|
||||
- [Allocation, construction, and destruction](#allocation-construction-and-destruction)
|
||||
- [Assignment, copying, and moving](#assignment-copying-and-moving)
|
||||
- [Moving](#moving)
|
||||
- [Comparison](#comparison)
|
||||
- [Implicit and explicit conversion](#implicit-and-explicit-conversion)
|
||||
- [Inline type composition](#inline-type-composition)
|
||||
@@ -599,8 +602,7 @@ fn RemoveLast(x: (Int, Int, Int)) -> (Int, Int) {
|
||||
|
||||
> References: [Classes](classes.md)
|
||||
|
||||
Classes are a way for users to define their own data strutures or named product
|
||||
types.
|
||||
Classes are a way for users to define their own data strutures or record types.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -620,11 +622,7 @@ Breaking apart `Widget`:
|
||||
- `Widget` has one `String` member: `payload`.
|
||||
- Given an instance `dial`, a member can be referenced with `dial.paylod`.
|
||||
|
||||
##### Allocation, construction, and destruction
|
||||
|
||||
> **TODO:** Needs a feature design and a high level summary provided inline.
|
||||
|
||||
##### Assignment, copying, and moving
|
||||
##### Assignment, copying
|
||||
|
||||
You may use a _structural data class literal_, also known as a _struct literal_,
|
||||
to assign or initialize a variable with a class type.
|
||||
@@ -641,6 +639,67 @@ var thingy: Widget = sprocket;
|
||||
sprocket = thingy;
|
||||
```
|
||||
|
||||
##### Member access
|
||||
|
||||
The data members of a variable with a class type may be accessed using dot `.`
|
||||
notation:
|
||||
|
||||
```carbon
|
||||
Assert(sprocket.x == thingy.x);
|
||||
```
|
||||
|
||||
##### Methods
|
||||
|
||||
Class type definitions can include methods:
|
||||
|
||||
```carbon
|
||||
class Point {
|
||||
fn Distance[me: Self](x2: i32, y2: i32) -> f32 {
|
||||
var dx: i32 = x2 - me.x;
|
||||
var dy: i32 = y2 - me.y;
|
||||
return Math.Sqrt(dx * dx - dy * dy);
|
||||
}
|
||||
fn Offset[addr me: Self*](dx: i32, dy: i32);
|
||||
|
||||
var x: i32;
|
||||
var y: i32;
|
||||
}
|
||||
|
||||
fn Point.Offset[addr me: Self*](dx: i32, dy: i32) {
|
||||
me->x += dx;
|
||||
me->y += dy;
|
||||
}
|
||||
|
||||
var origin: Point = {.x = 0, .y = 0};
|
||||
Assert(Math.Abs(origin.Distance(3, 4) - 5.0) < 0.001);
|
||||
origin.Offset(3, 4);
|
||||
Assert(origin.Distance(3, 4) == 0.0);
|
||||
```
|
||||
|
||||
This defines a `Point` class type with two integer data members `x` and `y` and
|
||||
two methods `Distance` and `Offset`:
|
||||
|
||||
- Methods are defined as functions with a `me` parameter inside square
|
||||
brackets `[`...`]` before the regular explicit parameter list in parens
|
||||
`(`...`)`.
|
||||
- Methods are called using using the member syntax, `origin.Distance(`...`)`
|
||||
and `origin.Offset(`...`)`.
|
||||
- `Distance` computes and returns the distance to another point, without
|
||||
modifying the `Point`. This is signified using `[me: Self]` in the method
|
||||
declaration.
|
||||
- `origin.Offset(`...`)` does modify the value of `origin`. This is signified
|
||||
using `[addr me: Self*]` in the method declaration.
|
||||
- Methods may be declared lexically inline like `Distance`, or lexically out
|
||||
of line like `Offset`.
|
||||
|
||||
##### Allocation, construction, and destruction
|
||||
|
||||
> **TODO:** Needs a feature design and a high level summary provided inline.
|
||||
|
||||
##### Moving
|
||||
|
||||
> **TODO:** Needs a feature design and a high level summary provided inline.
|
||||
|
||||
##### Comparison
|
||||
|
||||
> **TODO:** Needs a feature design and a high level summary provided inline.
|
||||
|
||||
+420
-84
@@ -30,18 +30,26 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Type expression](#type-expression)
|
||||
- [Assignment and initialization](#assignment-and-initialization)
|
||||
- [Operations performed field-wise](#operations-performed-field-wise)
|
||||
- [Future work](#future-work)
|
||||
- [Nominal class types](#nominal-class-types)
|
||||
- [Construction](#construction)
|
||||
- [Member type](#member-type)
|
||||
- [Nominal class types](#nominal-class-types)
|
||||
- [Forward declaration](#forward-declaration)
|
||||
- [Self](#self)
|
||||
- [Construction](#construction)
|
||||
- [Assignment](#assignment)
|
||||
- [Member functions](#member-functions)
|
||||
- [Class functions](#class-functions)
|
||||
- [Methods](#methods)
|
||||
- [Name lookup in member function definitions](#name-lookup-in-member-function-definitions)
|
||||
- [Nominal data classes](#nominal-data-classes)
|
||||
- [Member type](#member-type)
|
||||
- [Let](#let)
|
||||
- [Methods](#methods)
|
||||
- [Alias](#alias)
|
||||
- [Private access](#private-access)
|
||||
- [Future work](#future-work)
|
||||
- [Struct literal shortcut](#struct-literal-shortcut)
|
||||
- [Optional named parameters](#optional-named-parameters)
|
||||
- [Field defaults for struct types](#field-defaults-for-struct-types)
|
||||
- [Destructuring in pattern matching](#destructuring-in-pattern-matching)
|
||||
- [Discussion](#discussion)
|
||||
- [Access control](#access-control)
|
||||
- [Operator overloading](#operator-overloading)
|
||||
- [Inheritance](#inheritance)
|
||||
- [C++ abstract base classes interoperating with object-safe interfaces](#c-abstract-base-classes-interoperating-with-object-safe-interfaces)
|
||||
@@ -75,8 +83,9 @@ class variables.
|
||||
The use cases for classes include both cases motivated by C++ interop, and cases
|
||||
that we expect to be included in idiomatic Carbon-only code.
|
||||
|
||||
**This design currently only attempts to address the "data classes" use case.**
|
||||
Addressing the other use cases is future work.
|
||||
**This design currently only attempts to address the "data classes" and
|
||||
"encapsulated types without inheritance" use cases.** Addressing the other use
|
||||
cases is future work.
|
||||
|
||||
### Data classes
|
||||
|
||||
@@ -130,8 +139,7 @@ We expect two kinds of methods on these types: public methods defining the API
|
||||
for accessing and manipulating values of the type, and private helper methods
|
||||
used as an implementation detail of the public methods.
|
||||
|
||||
These types are expected in idiomatic Carbon-only code. Extending this design to
|
||||
support these types is future work.
|
||||
These types are expected in idiomatic Carbon-only code.
|
||||
|
||||
#### With inheritance and subtyping
|
||||
|
||||
@@ -509,8 +517,7 @@ but we expect later to support more ways to define data class types. Also note
|
||||
that there is no `struct` keyword, "struct" is just convenient shorthand
|
||||
terminology for a structural data class.
|
||||
|
||||
**Future work:** We intend to support nominal [data classes](#data-classes) as
|
||||
well.
|
||||
[Nominal data classes](#nominal-data-classes) are also supported by Carbon.
|
||||
|
||||
### Literals
|
||||
|
||||
@@ -635,68 +642,67 @@ Destruction is performed field-wise in reverse order.
|
||||
Extending user-defined operations on the fields to an operation on an entire
|
||||
data class is [future work](#interfaces-implemented-for-data-classes).
|
||||
|
||||
## Future work
|
||||
## Nominal class types
|
||||
|
||||
This includes features that need to be designed, questions to answer, and a
|
||||
description of the provisional syntax in use until these decisions have been
|
||||
made.
|
||||
The declarations for nominal class types will have:
|
||||
|
||||
### Nominal class types
|
||||
- `class` introducer
|
||||
- the name of the class
|
||||
- in the future, we will have optional modifiers for inheritance
|
||||
- `{`, an open curly brace
|
||||
- a sequence of declarations
|
||||
- `}`, a close curly brace
|
||||
|
||||
The declarations for nominal class types will have a different format.
|
||||
Provisionally we have been using something like this:
|
||||
Declarations should generally match declarations that can be declared in other
|
||||
contexts, for example variable declarations with `var` will define
|
||||
[instance variables](https://en.wikipedia.org/wiki/Instance_variable):
|
||||
|
||||
```
|
||||
class TextLabel {
|
||||
var x: Int;
|
||||
var y: Int;
|
||||
var x: i32;
|
||||
var y: i32;
|
||||
|
||||
var text: String;
|
||||
var text: String = "default";
|
||||
}
|
||||
```
|
||||
|
||||
It is an open question, though, how we will address the
|
||||
[different use cases](#use-cases). For example, we might mark
|
||||
[data classes](#data-classes) with an `impl as Data {}` line.
|
||||
The main difference here is that `"default"` is a default instead of an
|
||||
initializer, and will be ignored if another value is supplied for that field
|
||||
when constructing a value. Defaults must be constants whose value can be
|
||||
determined at compile time.
|
||||
|
||||
### Construction
|
||||
### Forward declaration
|
||||
|
||||
There are a variety of options for constructing class values, we might choose to
|
||||
support, including initializing from struct values:
|
||||
To support circular references between class types, we allow
|
||||
[forward declaration](https://en.wikipedia.org/wiki/Forward_declaration) of
|
||||
types. A type that is forward declared is considered incomplete until the end of
|
||||
a definition with the same name.
|
||||
|
||||
```
|
||||
var p1: Point2D = {.x = 1, .y = 2};
|
||||
var p2: auto = {.x = 1, .y = 2} as Point2D;
|
||||
var p3: auto = Point2D{.x = 1, .y = 2};
|
||||
var p4: auto = Point2D(1, 2);
|
||||
```
|
||||
class GraphNode;
|
||||
|
||||
### Member type
|
||||
class GraphEdge {
|
||||
var head: GraphNode*;
|
||||
var tail: GraphNode*;
|
||||
}
|
||||
|
||||
Additional types may be defined in the scope of a class definition.
|
||||
|
||||
```
|
||||
class StringCounts {
|
||||
class Node {
|
||||
var key: String;
|
||||
var count: Int;
|
||||
}
|
||||
var counts: Vector(Node);
|
||||
class GraphNode {
|
||||
var edges: Vector(GraphEdge*);
|
||||
}
|
||||
```
|
||||
|
||||
The inner type is a member of the type, and is given the name
|
||||
`StringCounts.Node`.
|
||||
**Open question:** What is specifically allowed and forbidden with an incomplete
|
||||
type has not yet been decided.
|
||||
|
||||
### Self
|
||||
|
||||
A `class` definition may provisionally include references to its own name in
|
||||
limited ways, similar to an incomplete type. What is allowed and forbidden is an
|
||||
open question.
|
||||
limited ways. These limitations arise from the type not being complete until the
|
||||
end of its definition is reached.
|
||||
|
||||
```
|
||||
class IntListNode {
|
||||
var data: Int;
|
||||
var data: i32;
|
||||
var next: IntListNode*;
|
||||
}
|
||||
```
|
||||
@@ -706,7 +712,7 @@ current type, is:
|
||||
|
||||
```
|
||||
class IntListNode {
|
||||
var data: Int;
|
||||
var data: i32;
|
||||
var next: Self*;
|
||||
}
|
||||
```
|
||||
@@ -716,48 +722,388 @@ class IntListNode {
|
||||
```
|
||||
class IntList {
|
||||
class IntListNode {
|
||||
var data: Int;
|
||||
var data: i32;
|
||||
var next: Self*;
|
||||
}
|
||||
var first: IntListNode*;
|
||||
}
|
||||
```
|
||||
|
||||
### Let
|
||||
### Construction
|
||||
|
||||
Other type constants can provisionally be defined using a `let` declaration:
|
||||
Any function with access to all the data fields of a class can construct one by
|
||||
converting a [struct value](#struct-types) to the class type:
|
||||
|
||||
```
|
||||
class MyClass {
|
||||
let Pi: Float32 = 3.141592653589793;
|
||||
let IndexType: Type = Int;
|
||||
var tl1: TextLabel = {.x = 1, .y = 2};
|
||||
var tl2: auto = {.x = 1, .y = 2} as TextLabel;
|
||||
|
||||
Assert(tl1.x == tl2.x);
|
||||
|
||||
fn ReturnsATextLabel() -> TextLabel {
|
||||
return {.x = 1, .y = 2};
|
||||
}
|
||||
var tl3: TextLabel = ReturnsATextLabel();
|
||||
|
||||
fn AcceptsATextLabel(tl: TextLabel) -> i32 {
|
||||
return tl.x + tl.y;
|
||||
}
|
||||
Assert(AcceptsATextLabel({.x = 2, .y = 4}) == 6);
|
||||
```
|
||||
|
||||
Note that a nominal class, unlike a [struct type](#type-expression), can define
|
||||
default values for fields, and so may be initialized with a
|
||||
[struct value](#literals) that omits some or all of those fields.
|
||||
|
||||
#### Assignment
|
||||
|
||||
Assignment to a struct value is also allowed in a function with access to all
|
||||
the data fields of a class. Assignment always overwrites all of the field
|
||||
members.
|
||||
|
||||
```
|
||||
var tl: TextLabel = {.x = 1, .y = 2};
|
||||
Assert(tl.text == "default");
|
||||
|
||||
// ✅ Allowed: assigns all fields
|
||||
tl = {.x = 3, .y = 4, .text = "new"};
|
||||
|
||||
// ✅ Allowed: This statement is evaluated in two steps:
|
||||
// 1. {.x = 5, .y = 6} is converted into a new TextLabel value,
|
||||
// using default for field `text`.
|
||||
// 2. tl is assigned to a TextLabel, which has values for all
|
||||
// fields.
|
||||
tl = {.x = 5, .y = 6};
|
||||
Assert(tl.text == "default");
|
||||
```
|
||||
|
||||
**Open question:** This behavior might be surprising because there is an
|
||||
ambiguity about whether to use the default value or the previous value for a
|
||||
field. We could require all fields to be specified when assigning, and only use
|
||||
field defaults when initializing a new value.
|
||||
|
||||
```
|
||||
// ❌ Forbidden: should tl.text == "default" or "new"?
|
||||
tl = {.x = 5, .y = 6};
|
||||
```
|
||||
|
||||
### Member functions
|
||||
|
||||
Member functions can either be class functions or methods. Class functions are
|
||||
members of the type, while methods can only be called on instances.
|
||||
|
||||
#### Class functions
|
||||
|
||||
A class function is like a
|
||||
[C++ static member function or method](<https://en.wikipedia.org/wiki/Static_(keyword)#Static_method>),
|
||||
and is declared like a function at file scope. The declaration can include a
|
||||
definition of the function body, or that definition can be provided out of line
|
||||
after the class definition is finished. A common use is for constructor
|
||||
functions.
|
||||
|
||||
```
|
||||
class Point {
|
||||
fn Origin() -> Self {
|
||||
return {.x = 0, .y = 0};
|
||||
}
|
||||
fn CreateCentered() -> Self;
|
||||
|
||||
var x: i32;
|
||||
var y: i32;
|
||||
}
|
||||
|
||||
fn Point.CreateCentered() -> Self {
|
||||
return {.x = ScreenWidth() / 2, .y = ScreenHeight() / 2};
|
||||
}
|
||||
```
|
||||
|
||||
There are definite questions about this syntax:
|
||||
Class functions are members of the type, and may be accessed as using dot `.`
|
||||
member access either the type or any instance.
|
||||
|
||||
- Should these use the `:!` generic syntax decided in
|
||||
[issue #565](https://github.com/carbon-language/carbon-lang/issues/565)?
|
||||
- Would we also have `alias` declarations? They would only be used for names,
|
||||
not other constant values.
|
||||
```
|
||||
var p1: Point = Point.Origin();
|
||||
var p2: Point = p1.CreateCentered();
|
||||
```
|
||||
|
||||
### Methods
|
||||
#### Methods
|
||||
|
||||
A future proposal will incorporate
|
||||
[method](<https://en.wikipedia.org/wiki/Method_(computer_programming)>)
|
||||
declaration, definition, and calling into classes. The syntax for declaring
|
||||
methods has been decided in
|
||||
[question-for-leads issue #494](https://github.com/carbon-language/carbon-lang/issues/494).
|
||||
Summarizing that issue:
|
||||
[Method](<https://en.wikipedia.org/wiki/Method_(computer_programming)>)
|
||||
declarations are distinguished from [class function](#class-functions)
|
||||
declarations by having a `me` parameter in square brackets `[`...`]` before the
|
||||
explicit parameter list in parens `(`...`)`. There is no implicit member access
|
||||
in methods, so inside the method body members are accessed through the `me`
|
||||
parameter. Methods may be written lexically inline or after the class
|
||||
declaration.
|
||||
|
||||
- Accessors are written: `fn Diameter[me: Self]() -> Float { ... }`
|
||||
- Mutators are written: `fn Expand[addr me: Self*](distance: Float) { ... }`
|
||||
- Associated functions that don't take a receiver at all, like
|
||||
[C++'s static methods](<https://en.wikipedia.org/wiki/Static_(keyword)#Static_method>),
|
||||
are written: `fn Create() -> Self { ... }`
|
||||
```carbon
|
||||
class Circle {
|
||||
fn Diameter[me: Self]() -> f32 {
|
||||
return me.radius * 2;
|
||||
}
|
||||
fn Expand[addr me: Self*](distance: f32);
|
||||
|
||||
We do not expect to have implicit member access in methods, so inside the method
|
||||
body members will be accessed through the `me` parameter.
|
||||
var center: Point;
|
||||
var radius: f32;
|
||||
}
|
||||
|
||||
fn Circle.Expand[addr me: Self*](distance: f32) {
|
||||
me->radius += distance;
|
||||
}
|
||||
|
||||
var c: Circle = {.center = Point.Origin(), .radius = 1.5 };
|
||||
Assert(Math.Abs(c.Diameter() - 3.0) < 0.001);
|
||||
c.Expand(0.5);
|
||||
Assert(Math.Abs(c.Diameter() - 4.0) < 0.001);
|
||||
```
|
||||
|
||||
- Methods are called using using the dot `.` member syntax, `c.Diameter()` and
|
||||
`c.Expand(`...`)`.
|
||||
- `Diameter` computes and returns the diameter of the circle without modifying
|
||||
the `Circle` instance. This is signified using `[me: Self]` in the method
|
||||
declaration.
|
||||
- `c.Expand(`...`)` does modify the value of `c`. This is signified using
|
||||
`[addr me: Self*]` in the method declaration.
|
||||
|
||||
The pattern '`addr` _patt_' means "first take the address of the argument, which
|
||||
must be an
|
||||
[l-value](<https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue>), and
|
||||
then match pattern _patt_ against it".
|
||||
|
||||
If the method declaration also includes
|
||||
[deduced generic parameters](/docs/design/generics/overview.md#deduced-parameters),
|
||||
the `me` parameter must be in the same list in square brackets `[`...`]`. The
|
||||
`me` parameter may appear in any position in that list, as long as it appears
|
||||
after any names needed to describe its type.
|
||||
|
||||
#### Name lookup in member function definitions
|
||||
|
||||
When defining a member function lexically inline, we delay type checking of the
|
||||
function body until the definition of the current type is complete. This means
|
||||
that name lookup _for members of objects_ is also delayed. That means that you
|
||||
can reference `me.F()` in a lexically inline method definition even before the
|
||||
declaration of `F` in that class definition. However, other names still need to
|
||||
be declared before they are used. This includes unqualified names, names within
|
||||
namespaces, and names _for members of types_.
|
||||
|
||||
```
|
||||
class Point {
|
||||
fn Distance[me: Self]() -> f32 {
|
||||
// ✅ Allowed: `x` and `y` are names for members of an object,
|
||||
// and so lookup is delayed until `type_of(me) == Self` is complete.
|
||||
return Math.Sqrt(me.x * me.x + me.y * me.y);
|
||||
}
|
||||
|
||||
fn CreatePolarInvalid(r: f32, theta: f32) -> Point {
|
||||
// ❌ Forbidden: unqualified name used before declaration.
|
||||
return Create(r * Math.Cos(theta), r * Math.Sin(theta));
|
||||
}
|
||||
fn CreatePolarValid1(r: f32, theta: f32) -> Point {
|
||||
// ❌ Forbidden: `Create` is not yet declared.
|
||||
return Point.Create(r * Math.Cos(theta), r * Math.Sin(theta));
|
||||
}
|
||||
fn CreatePolarValid2(r: f32, theta: f32) -> Point {
|
||||
// ❌ Forbidden: `Create` is not yet declared.
|
||||
return Self.Create(r * Math.Cos(theta), r * Math.Sin(theta));
|
||||
}
|
||||
|
||||
fn Create(x: f32, y: f32) -> Point {
|
||||
// ✅ Allowed: checking that conversion of `{.x: f32, .y: f32}`
|
||||
// to `Point` is delayed until `Point` is complete.
|
||||
return {.x = x, .y = y};
|
||||
}
|
||||
|
||||
fn CreateXEqualsY(xy: f32) -> Point {
|
||||
// ✅ Allowed: `Create` is declared earlier.
|
||||
return Create(xy, xy);
|
||||
}
|
||||
|
||||
fn CreateXAxis(x: f32) -> Point;
|
||||
|
||||
fn Angle[me: Self]() -> f32;
|
||||
|
||||
var x: f32;
|
||||
var y: f32;
|
||||
}
|
||||
|
||||
fn Point.CreateXAxis(x: f32) -> Point;
|
||||
// ✅ Allowed: `Point` type is complete.
|
||||
// Members of `Point` like `Create` are in scope.
|
||||
return Create(x, 0);
|
||||
}
|
||||
|
||||
fn Point.Angle[me: Self]() -> f32 {
|
||||
// ✅ Allowed: `Point` type is complete.
|
||||
// Function is checked immediately.
|
||||
return Math.ATan2(me.y, me.x);
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The details of name lookup are still being decided in issue
|
||||
[#472: Open question: Calling functions defined later in the same file](https://github.com/carbon-language/carbon-lang/issues/472).
|
||||
|
||||
### Nominal data classes
|
||||
|
||||
We will mark [data classes](#data-classes) with an `impl as Data {}` line.
|
||||
|
||||
```
|
||||
class TextLabel {
|
||||
var x: i32;
|
||||
var y: i32;
|
||||
|
||||
var text: String;
|
||||
|
||||
// This line makes `TextLabel` a data class, which defines
|
||||
// a number of operations field-wise.
|
||||
impl as Data {}
|
||||
}
|
||||
```
|
||||
|
||||
The fields of data classes must all be public. That line will add
|
||||
[field-wise implementations and operations of all interfaces that a struct with the same fields would get by default](#operations-performed-field-wise).
|
||||
|
||||
The word `Data` here refers to an empty interface in the Carbon prologue. That
|
||||
interface would then be part of our
|
||||
[strategy for defining how other interfaces are implemented for data classes](#interfaces-implemented-for-data-classes).
|
||||
|
||||
**References:** Rationale for this approach is given in proposal
|
||||
[#722](/proposals/p0722.md#nominal-data-class).
|
||||
|
||||
### Member type
|
||||
|
||||
Additional types may be defined in the scope of a class definition.
|
||||
|
||||
```
|
||||
class StringCounts {
|
||||
class Node {
|
||||
var key: String;
|
||||
var count: i32;
|
||||
}
|
||||
var counts: Vector(Node);
|
||||
}
|
||||
```
|
||||
|
||||
The inner type is a member of the type, and is given the name
|
||||
`StringCounts.Node`. This case is called a _member class_ since the type is a
|
||||
class, but other kinds of type declarations, like choice types, are allowed.
|
||||
|
||||
### Let
|
||||
|
||||
Other type constants can be defined using a `let` declaration:
|
||||
|
||||
```
|
||||
class MyClass {
|
||||
let Pi:! f32 = 3.141592653589793;
|
||||
let IndexType:! Type = i32;
|
||||
}
|
||||
```
|
||||
|
||||
The `:!` indicates that this is defining a compile-time constant, and so does
|
||||
not affect the storage of instances of that class.
|
||||
|
||||
### Alias
|
||||
|
||||
You may declare aliases of the names of class members. This is to allow them to
|
||||
be renamed in multiple steps or support alternate names.
|
||||
|
||||
```
|
||||
class StringPair {
|
||||
var key: String;
|
||||
var value: String;
|
||||
alias first = key;
|
||||
alias second = value;
|
||||
}
|
||||
|
||||
var sp1: StringPair = {.key = "K", .value = "1"};
|
||||
var sp2: StringPair = {.first = "K", .second = "2"};
|
||||
Assert(sp1.first == sp2.key);
|
||||
Assert(&sp1.first == &sp1.key);
|
||||
```
|
||||
|
||||
**Future work:** This needs to be connected to the broader design of aliases,
|
||||
once that lands.
|
||||
|
||||
### Private access
|
||||
|
||||
By default, all members of a class are fully publicly accessible. Access can be
|
||||
restricted by adding a keyword, called an
|
||||
[access modifier](https://en.wikipedia.org/wiki/Access_modifiers), prior to the
|
||||
declaration. Access modifiers are how Carbon supports
|
||||
[encapsulation](#encapsulated-types).
|
||||
|
||||
```carbon
|
||||
class Point {
|
||||
fn Distance[me: Self]() -> f32;
|
||||
// These are only accessible to members of `Point`.
|
||||
private var x: f32;
|
||||
private var y: f32;
|
||||
}
|
||||
```
|
||||
|
||||
As in C++, `private` means only accessible to members of the class.
|
||||
|
||||
**Future work:** We will add support for `protected` access when inheritance is
|
||||
added to this design. We will also define a convenient way for tests that belong
|
||||
to the same library to get access to private members.
|
||||
|
||||
**Future work:** `private` will give the member internal linkage unless it needs
|
||||
to be external because it is used in an inline method or template. We may in the
|
||||
future
|
||||
[add a way to specify internal linkage explicitly](/proposals/p0722.md#specifying-linkage-as-part-of-the-access-modifier).
|
||||
|
||||
**Open questions:** Using `private` to mean "restricted to this class" matches
|
||||
C++. Other languages support restricting to different scopes:
|
||||
|
||||
- Swift supports "restrict to this module" and "restrict to this file".
|
||||
- Rust supports "restrict to this module and any children of this module", as
|
||||
well as "restrict to this crate", "restrict to parent module", and "restrict
|
||||
to a specific ancestor module".
|
||||
|
||||
**Comparison to other languages:** C++, Rust, and Swift all make class members
|
||||
private by default. C++ offers the `struct` keyword that makes members public by
|
||||
default.
|
||||
|
||||
**Rationale:** Carbon makes members public by default for a few reasons:
|
||||
|
||||
- The readability of public members is the most important, since we expect
|
||||
most readers to be concerned with the public API of a type.
|
||||
- The members that are most commonly private are the data fields, which have
|
||||
relatively less complicated definitions that suffer less from the extra
|
||||
annotation.
|
||||
|
||||
Additionally, there is precedent for this approach in modern object-oriented
|
||||
languages such as
|
||||
[Kotlin](https://kotlinlang.org/docs/visibility-modifiers.html) and
|
||||
[Python](https://docs.python.org/3/tutorial/classes.html), both of which are
|
||||
well regarded for their usability.
|
||||
|
||||
Keywords controlling visibility are attached to individual declarations instead
|
||||
of C++'s approach of labels controlling the visibility for all following
|
||||
declarations to
|
||||
[reduce context sensitivity](/docs/project/principles/low_context_sensitivity.md).
|
||||
This matches
|
||||
[Rust](https://doc.rust-lang.org/reference/visibility-and-privacy.html),
|
||||
[Swift](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html),
|
||||
[Java](http://rosettacode.org/wiki/Classes#Java),
|
||||
[C#](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers),
|
||||
[Kotlin](https://kotlinlang.org/docs/visibility-modifiers.html#classes-and-interfaces),
|
||||
and [D](https://wiki.dlang.org/Access_specifiers_and_visibility).
|
||||
|
||||
**References:** Proposal
|
||||
[#561: Basic classes](https://github.com/carbon-language/carbon-lang/pull/561)
|
||||
included the decision that
|
||||
[members default to publicly accessible](/proposals/p0561.md#access-control)
|
||||
originally asked in issue
|
||||
[#665](https://github.com/carbon-language/carbon-lang/issues/665).
|
||||
|
||||
## Future work
|
||||
|
||||
This includes features that need to be designed, questions to answer, and a
|
||||
description of the provisional syntax in use until these decisions have been
|
||||
made.
|
||||
|
||||
### Struct literal shortcut
|
||||
|
||||
We could allow you to write `{x, y}` as a short hand for `{.x = x, .y = y}`.
|
||||
|
||||
### Optional named parameters
|
||||
|
||||
@@ -837,16 +1183,6 @@ Some discussion on this topic has occurred in:
|
||||
[2](https://docs.google.com/document/d/1u6GORSkcgThMAiYKOqsgALcEviEtcghGb5TTVT-U-N0/edit)
|
||||
- ["match" in syntax choices doc](https://docs.google.com/document/d/1iuytei37LPg_tEd6xe-O6P_bpN7TIbEjNtFMLYW2Nno/edit#heading=h.y566d16ivoy2)
|
||||
|
||||
### Access control
|
||||
|
||||
We will need some way of controlling access to the members of classes. By
|
||||
default, all members are fully publicly accessible, as decided in
|
||||
[issue #665](https://github.com/carbon-language/carbon-lang/issues/665).
|
||||
|
||||
The set of access control options Carbon will support is an open question. Swift
|
||||
and C++ (especially w/ modules) provide a lot of options and a pretty wide space
|
||||
to explore here.
|
||||
|
||||
### Operator overloading
|
||||
|
||||
This includes destructors, copy and move operations, as well as other Carbon
|
||||
|
||||
@@ -67,5 +67,6 @@ request:
|
||||
- [0646 - Low context-sensitivity principle](p0646.md)
|
||||
- [0676 - `:!` generic syntax](p0676.md)
|
||||
- [0680 - And, or, not](p0680.md)
|
||||
- [0722 - Nominal classes and methods](p0722.md)
|
||||
|
||||
<!-- endproposals -->
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
# Nominal classes and methods
|
||||
|
||||
<!--
|
||||
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/722)
|
||||
|
||||
<!-- 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)
|
||||
- [Method syntax](#method-syntax)
|
||||
- [Rationale](#rationale)
|
||||
- [Full receiver type](#full-receiver-type)
|
||||
- [Method names line up](#method-names-line-up)
|
||||
- [Receiver in square brackets](#receiver-in-square-brackets)
|
||||
- [Receiver parameter is named `me`](#receiver-parameter-is-named-me)
|
||||
- [Keyword to indicate pass by address](#keyword-to-indicate-pass-by-address)
|
||||
- [Marking mutating methods at the call site](#marking-mutating-methods-at-the-call-site)
|
||||
- [Differences between functions and methods](#differences-between-functions-and-methods)
|
||||
- [Specifying linkage as part of the access modifier](#specifying-linkage-as-part-of-the-access-modifier)
|
||||
- [Nominal data class](#nominal-data-class)
|
||||
- [Let constants](#let-constants)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Problem
|
||||
|
||||
We need facilities for defining new nominal
|
||||
[record types](<https://en.wikipedia.org/wiki/Record_(computer_science)>) in
|
||||
Carbon. They will need to support object-oriented features such as methods.
|
||||
|
||||
## Background
|
||||
|
||||
This is a follow up to
|
||||
[#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561)
|
||||
which laid a foundation.
|
||||
|
||||
## 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/701).
|
||||
|
||||
## Rationale based on Carbon's goals
|
||||
|
||||
This particular proposal is focusing on [the Carbon
|
||||
goal](/docs/project/goals.md#code-that-is-easy-to-read-understand-and -write)
|
||||
that code is "easy to read, understand, and write." Future proposals will
|
||||
address other aspects of the class type design such as performance.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### Method syntax
|
||||
|
||||
The proposed method syntax was decided in question-for-leads issue
|
||||
[#494: Method syntax](https://github.com/carbon-language/carbon-lang/issues/494).
|
||||
|
||||
A wide variety of alternatives were considered:
|
||||
|
||||
- Using a different introducer such as `method` to distinguish methods from
|
||||
functions.
|
||||
- Putting a pattern to match the receiver before the method name.
|
||||
- Omitting either the receiver name or the receiver type.
|
||||
- Allowing the user to specify the receiver name instead of using a parameter
|
||||
named `me` to indicate it is a method.
|
||||
- Using syntax between `fn` and the method name to indicate that it is a
|
||||
method, and whether the method takes a value or the address of the receiver.
|
||||
- Using a keyword to indicate that the first parameter is the receiver, as in
|
||||
[C++'s "Deducing `this`" proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r6.html).
|
||||
- Using a delimiter in the parameter list after the receiver type, but the
|
||||
main choice of `;` seemed pretty subtle.
|
||||
- Putting the receiver pattern in a separate set of brackets. This could be an
|
||||
additional set of parens `(`...`)` before the explicit parameter list. Or
|
||||
the deduced parameters could be put in angle brackets `<`...`>` and the
|
||||
receiver in square brackes `[`...`]`.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
method (this: Self*) Set(n: Int);
|
||||
fn [Me*].Set(n: Int);
|
||||
fn ->Set(n: Int);
|
||||
fn& Set(n: Int);
|
||||
fn &Set(s: Self*, n: Int);
|
||||
fn Set(this self: Self*, n: Int);
|
||||
fn Set[this self: Self*](n: Int);
|
||||
fn Set(self: Self*; n: Int);
|
||||
fn Set(self: Self*)(n: Int);
|
||||
```
|
||||
|
||||
Note: These examples are written using the parameter syntax decided in
|
||||
[issue #542](https://github.com/carbon-language/carbon-lang/issues/542), though
|
||||
that does not match the syntax used in the discussion at the time.
|
||||
|
||||
#### Rationale
|
||||
|
||||
##### Full receiver type
|
||||
|
||||
We wanted the option to specify the type of the receiver. In C++, the type of
|
||||
this is controlled by way of special syntax, such as putting a `const` keyword
|
||||
at the end of the method declaration. We wanted to treat the receiver type more
|
||||
consistently with other parameter types, like is being considered in
|
||||
[C++'s "Deducing `this`" proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r6.html).
|
||||
That proposal also showed the value of including the entire type, rather than
|
||||
just part such as whether the parameter would be passed by value or address. We
|
||||
already plan to use this feature to conditionally include methods based on
|
||||
whether parameters to the type satisfy additional constraints. In this example,
|
||||
`FixedArray(T, N)` has a `Print()` method if `T` is `Printable`:
|
||||
|
||||
```
|
||||
class FixedArray(T:! Type, N:! Int) {
|
||||
// ...
|
||||
fn Print[me: FixedArray(P:! Printable, N)]() { ... }
|
||||
}
|
||||
```
|
||||
|
||||
We did consider the option of making the type of the receiver optional. This is
|
||||
not what we decided to try first, but is an idea we would consider in the
|
||||
future.
|
||||
|
||||
##### Method names line up
|
||||
|
||||
Using the same introducer `fn` as functions, as decided in
|
||||
[issue #463](https://github.com/carbon-language/carbon-lang/issues/463), along
|
||||
with the method name immediately afterwards means that function and method names
|
||||
line up in the same column visually.
|
||||
|
||||
```
|
||||
struct IntContainer {
|
||||
// Non-methods for building instances
|
||||
fn MakeFromInts ... -> IntContainer;
|
||||
fn MakeRepeating ... -> IntContainer;
|
||||
|
||||
// Methods
|
||||
fn Size ... -> Int;
|
||||
fn First ... -> Int;
|
||||
|
||||
fn Clear ...;
|
||||
fn Append ...;
|
||||
}
|
||||
```
|
||||
|
||||
This is to ease scanning and to put the most important information up front.
|
||||
|
||||
We also considered using another 2-character introducer that would distinguish
|
||||
methods from associated functions, like `me`, but it did not garner much
|
||||
support. The name `me` was not evocative enough of "method", and people
|
||||
generally preferred matching other function declarations. We also considered
|
||||
using different introducers to communicate whether the receiver was passed by
|
||||
value or by address, for example `ro` for "read-only" and `rw` for "read-write".
|
||||
|
||||
##### Receiver in square brackets
|
||||
|
||||
Putting the receiver pattern inside the explicit parameter list, as is done in
|
||||
Python, would lead to an unfortunate mismatch between the arguments listed at
|
||||
the call and the function declaration. That motivated putting the receiver
|
||||
pattern inside square brackets (`[`...`]`) with deduced parameters. There were
|
||||
concerns, however, that this parameter did not act like deduced parameters.
|
||||
|
||||
##### Receiver parameter is named `me`
|
||||
|
||||
Having the receiver name be fixed is helpful for consistency. This benefits
|
||||
readers, and simplfies copying or moving code between functions. A fixed
|
||||
receiver name also allows us to identify the receiver pattern in the function
|
||||
declaration, and identify it as a method instead of an ordinary function
|
||||
associated with the type, like
|
||||
[static methods in C++](<https://en.wikipedia.org/wiki/Static_(keyword)#Static_method>).
|
||||
|
||||
Finally, we wanted the bodies of methods to access members of the object using
|
||||
an explicit member access. This means we need the name used to access members to
|
||||
be short, and so we chose the name `me`.
|
||||
|
||||
##### Keyword to indicate pass by address
|
||||
|
||||
We considered using just whether the receiver type was a pointer type to
|
||||
indicate whether the receiver was passed by address, but we preferred type
|
||||
information to flow in one direction. Otherwise this would cause a problem if we
|
||||
want to allow _deduction_ of the object parameter type. This deduction can't be
|
||||
used to select between pointer and not pointer.
|
||||
|
||||
We also considered supporting reference types that could bind to lvalues without
|
||||
explicitly taking the address of the object, but references would have added a
|
||||
lot of complexity to the type system and we believed that they are not otherwise
|
||||
necessary.
|
||||
|
||||
We decided to adopt an approach where the parameter declaration can contain a
|
||||
marker for matching an argument's
|
||||
[value category](https://en.cppreference.com/w/cpp/language/value_category)
|
||||
separately from its type. This marker would be present for methods that mutate
|
||||
the object, and so requires that the receiver be an lvalue to match the pattern.
|
||||
The marker means "first take the address of the argument and then match the rest
|
||||
of the pattern." We considered a few different possible ways to write this:
|
||||
|
||||
```
|
||||
fn Set[&me: Self*](n: Int);
|
||||
fn Set[*(me: Self*)](n: Int);
|
||||
fn Set[ref me: Self*](n: Int);
|
||||
```
|
||||
|
||||
We eventually settled on using a keyword `addr`.
|
||||
|
||||
```
|
||||
fn Set[addr me: Self*](n: Int);
|
||||
```
|
||||
|
||||
This doesn't use up a symbol, makes it easier to find in search engines, and
|
||||
allows us to add more keywords in the future for other calling conventions, such
|
||||
as `inout`. We may even find that those other keywords have preferable semantics
|
||||
so we may eventually drop `addr`.
|
||||
|
||||
This keyword approach also is consistent with how we are marking template
|
||||
parameters with the `template` keyword, as decided in
|
||||
[issue #565 on generic syntax](https://github.com/carbon-language/carbon-lang/issues/565).
|
||||
|
||||
### Marking mutating methods at the call site
|
||||
|
||||
We considered making it visible at the call site when the receiver was passed by
|
||||
address, with the address-of operator `&`.
|
||||
|
||||
```
|
||||
(&x).Set(4);
|
||||
```
|
||||
|
||||
This would in effect make the mutating methods be methods on the pointer type
|
||||
rather than the class type.
|
||||
|
||||
### Differences between functions and methods
|
||||
|
||||
Question-for-leads issue
|
||||
[#494: Method syntax](https://github.com/carbon-language/carbon-lang/issues/494)
|
||||
also considered what would be different between functions and methods:
|
||||
|
||||
- Methods use a different calling syntax: `x.F(n)`.
|
||||
- Methods distinguish between taking the receiver (`x`) by value or by
|
||||
pointer, without changing the call syntax.
|
||||
- Methods have to be declared in the body of the class.
|
||||
- Methods and associated functions both have access to private members of the
|
||||
class.
|
||||
- Only methods can opt in to using dynamic dispatch.
|
||||
- The receiver parameter to a method varies covariantly in inheritance unlike
|
||||
other parameter types.
|
||||
|
||||
### Specifying linkage as part of the access modifier
|
||||
|
||||
We considered various access modifiers such as `internal` or `private.internal`
|
||||
that would enforce internal linkage in addition to restricted visibility. We
|
||||
decided to postpone such considerations for the time being.
|
||||
|
||||
Even if we _do_ need explicit controls here, we did not feel the need to
|
||||
front-load that complexity right now and with relatively less time or experience
|
||||
to evaluate the tradeoffs. If and when linkage becomes a visible issue and
|
||||
important to discuss, we can add it. Until then, we can see how much mileage we
|
||||
can get out of purely implementation techniques.
|
||||
|
||||
### Nominal data class
|
||||
|
||||
We needed some way of opting a nominal class into the fieldwise behavior of a
|
||||
data class. [In Kotlin](https://kotlinlang.org/docs/data-classes.html), you
|
||||
precede the `class` declaration with a `data` keyword. Carbon already has a way
|
||||
of marking types as having specific semantic properties, implementing
|
||||
interfaces. This leverages the support for interfaces in the language to be able
|
||||
to express things like "here is a blanket implementation of an interface for all
|
||||
data classes" in a consistent way.
|
||||
|
||||
We chose the name `Data` rather than `DataClass` for the interface since tuples
|
||||
implicitly implement the interface and were "product types" rather than
|
||||
"classes".
|
||||
|
||||
### Let constants
|
||||
|
||||
We wanted a consistent interpretation for `let` declarations in classes and
|
||||
function bodies. Experience from C++ `const int` variables, where we try
|
||||
constant-evaluation and then give the program different semantics based on
|
||||
whether such evaluation happened to work, suggested we wanted to instead require
|
||||
`:!` in all places where a value that's usable during compilation is introduced.
|
||||
Reference in New Issue
Block a user