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:
josh11b
2021-08-23 15:45:41 -07:00
committed by GitHub
co-authored by Chandler Carruth
parent d71f5b1784
commit 0820dec01f
5 changed files with 772 additions and 92 deletions
+1
View File
@@ -4,6 +4,7 @@
circularly
copyable
crate
inout
pullrequest
statics
+67 -8
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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 -->
+283
View File
@@ -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.