Updating self syntax and adding static member variables (#7016)

Update the syntax for class (and interface/`impl`) methods to move
`self` into
the parameter parentheses `()` and make the type in its binding optional
(defaulting to `Self`). Introduce the `static` keyword for non-instance
member
variables to indicate static storage. Reflects the decision in leads
issue
[#6931](https://github.com/carbon-language/carbon-lang/issues/6931).

Updates the directly relevant design, but leaves a systematic update of
examples
to a future PR.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
Chandler Carruth
2026-05-13 01:30:44 +00:00
committed by GitHub
co-authored by Geoff Romer Richard Smith
parent 71eed5b04a
commit 10b2b71847
8 changed files with 588 additions and 119 deletions
+26 -25
View File
@@ -64,7 +64,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [User-defined types](#user-defined-types)
- [Classes](#classes)
- [Assignment](#assignment)
- [Class functions and factory functions](#class-functions-and-factory-functions)
- [Non-instance member functions](#non-instance-member-functions)
- [Methods](#methods)
- [Inheritance](#inheritance)
- [Access control](#access-control)
@@ -972,7 +972,7 @@ _incomplete_, and in some cases there are limitations on what can be done with
an incomplete name. Within a definition, the defined name is incomplete until
the end of the definition is reached, but is complete in the bodies of member
functions because they are
[parsed as if they appeared after the definition](#class-functions-and-factory-functions).
[parsed as if they appeared after the definition](classes.md#deferred-member-function-definitions).
A name is valid until the end of the innermost enclosing
[_scope_](<https://en.wikipedia.org/wiki/Scope_(computer_science)>). There are a
@@ -1655,12 +1655,12 @@ The order of the field declarations determines the fields' memory-layout order.
Classes may have other kinds of members beyond fields declared in its scope:
- [Class functions](#class-functions-and-factory-functions)
- [Non-instance member functions](#non-instance-member-functions)
- [Methods](#methods)
- [`alias`](#aliases)
- [`let`](#constant-let-declarations) to define class constants. **TODO:**
Another syntax to define constants associated with the class like
`class let` or `static let`?
`static let`?
- `class`, to define a
[_member class_ or _nested class_](https://en.wikipedia.org/wiki/Inner_class)
@@ -1705,20 +1705,20 @@ sprocket = {.x = 2, .y = 1, .payload = "Bounce"};
> - Proposal
> [#981: Implicit conversions for aggregates](https://github.com/carbon-language/carbon-lang/pull/981)
#### Class functions and factory functions
#### Non-instance member functions
Classes may also contain _class functions_. These are functions that are
accessed as members of the type, like
Classes may also contain _non-instance member functions_. These are functions
that are accessed as members of the type, like
[static member functions in C++](<https://en.wikipedia.org/wiki/Method_(computer_programming)#Static_methods>),
as opposed to [methods](#methods) that are members of instances. They are
commonly used to define a function that creates instances. Carbon does not have
separate
commonly used to define a function that creates instances (sometimes called
factory functions). Carbon does not have separate
[constructors](<https://en.wikipedia.org/wiki/Constructor_(object-oriented_programming)>)
like C++ does.
```carbon
class Point {
// Class function that instantiates `Point`.
// Non-instance member function that instantiates `Point`.
// `Self` in class scope means the class currently being defined.
fn Origin() -> Self {
return {.x = 0, .y = 0};
@@ -1762,20 +1762,20 @@ Class type definitions can include methods:
```carbon
class Point {
// Method defined inline
fn Distance[self: Self](x2: i32, y2: i32) -> f32 {
fn Distance(self, x2: i32, y2: i32) -> f32 {
var dx: i32 = x2 - self.x;
var dy: i32 = y2 - self.y;
return Math.Sqrt(dx * dx + dy * dy);
}
// Mutating method declaration
fn Offset[ref self: Self](dx: i32, dy: i32);
fn Offset(ref self, dx: i32, dy: i32);
var x: i32;
var y: i32;
}
// Out-of-line definition of method declared inline
fn Point.Offset[ref self: Self](dx: i32, dy: i32) {
fn Point.Offset(ref self, dx: i32, dy: i32) {
self.x += dx;
self.y += dy;
}
@@ -1789,17 +1789,16 @@ 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 class functions with a `self` parameter inside square
brackets `[`...`]` before the regular explicit parameter list in parens
`(`...`)`.
- Methods are defined by declaring `self` as the first parameter in the
parameter list in parens `(`...`)`.
- Methods are called 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 `[self: Self]` in the method
modifying the `Point`. This is signified using `self` in the method
declaration.
- `origin.Offset(`...`)` _does_ modify the value of `origin`. This is
signified using `[ref self: Self]` in the method declaration. It may only be
called on [reference expressions](#expression-categories).
signified using `ref self` in the method declaration. It may only be called
on [reference expressions](#expression-categories).
- Methods may be declared lexically inline like `Distance`, or lexically out
of line like `Offset`.
@@ -1808,6 +1807,8 @@ two methods `Distance` and `Offset`:
> - [Methods](classes.md#methods)
> - Proposal
> [#722: Nominal classes and methods](https://github.com/carbon-language/carbon-lang/pull/722)
> - Proposal
> [#7016: Updating `self` syntax and adding `static` fields](https://github.com/carbon-language/carbon-lang/pull/7016)
#### Inheritance
@@ -1954,13 +1955,13 @@ names resolvable by the compiler, and don't act like forward declarations.
#### Destructors
A destructor for a class is custom code executed when the lifetime of a value of
that type ends. They are defined with `fn destroy` followed by either
`[self: Self]` or `[ref self: Self]` (as is done with [methods](#methods)) and
the block of code in the class definition, as in:
that type ends. They are defined with `fn destroy` followed by either `self` or
`ref self` in the parameter list (as is done with [methods](#methods)) and the
block of code in the class definition, as in:
```carbon
class MyClass {
fn destroy[self: Self]() { ... }
fn destroy(self) { ... }
}
```
@@ -1969,7 +1970,7 @@ or:
```carbon
class MyClass {
// Can modify `self` in the body.
fn destroy[ref self: Self]() { ... }
fn destroy(ref self) { ... }
}
```
@@ -2169,7 +2170,7 @@ names earlier in the source than they are declared. In executable scopes such as
function bodies, names declared later are not found. In declarative scopes such
as packages, classes, and interfaces, it is an error to refer to names declared
later, except that inline class member function bodies are
[parsed as if they appeared after the class](#class-functions-and-factory-functions).
[parsed as if they appeared after the class](classes.md#deferred-member-function-definitions).
A name in Carbon is formed from a sequence of letters, numbers, and underscores,
and starts with a letter. We intend to follow
+144 -70
View File
@@ -30,13 +30,17 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Assignment and initialization](#assignment-and-initialization)
- [Operations performed field-wise](#operations-performed-field-wise)
- [Nominal class types](#nominal-class-types)
- [Fields](#fields)
- [Member variables](#member-variables)
- [Fields](#fields)
- [Static member variables](#static-member-variables)
- [Initializers](#initializers)
- [Syntax](#syntax)
- [Forward declaration](#forward-declaration)
- [`Self`](#self)
- [Construction](#construction)
- [Assignment](#assignment)
- [Member functions](#member-functions)
- [Class functions](#class-functions)
- [Non-methods](#non-methods)
- [Methods](#methods)
- [Deferred member function definitions](#deferred-member-function-definitions)
- [Name lookup in classes](#name-lookup-in-classes)
@@ -69,7 +73,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Discussion](#discussion)
- [Inheritance](#inheritance-1)
- [C++ abstract base classes interoperating with object-safe interfaces](#c-abstract-base-classes-interoperating-with-object-safe-interfaces)
- [Overloaded methods](#overloaded-methods)
- [Overloaded member functions](#overloaded-member-functions)
- [Interop with C++ inheritance](#interop-with-c-inheritance)
- [Virtual base classes](#virtual-base-classes)
- [Mixins](#mixins-1)
@@ -695,31 +699,68 @@ Declarations within a class should generally have the same syntax as
declarations that occur in other contexts. For example, member functions are
introduced with `fn`.
### Fields
### Member variables
Fields of a nominal class type are declared with `var`:
_Member variables_ are any `var`s declared within a class context (or in the
future, potentially an interface or `impl` context).
```
#### Fields
Instance member variables, or _fields_, are declared with `var`. They are
associated with instances of the class, and determine the data layout of those
instances.
#### Static member variables
Non-instance member variables, or _static member variables_, are declared with
`static var`. They are associated with the type itself rather than instances of
the type, and have static storage duration.
**Future work:** We should have a specific design for _storage duration_ in
Carbon, and that should clarify what _static storage duration_ means. The intent
is that it matches the general definition of
[static variables](https://en.wikipedia.org/wiki/Static_variable).
```carbon
class TextLabel {
var x: i32;
var y: i32;
// Static member variable.
static var count: i32;
var text: String = "default";
}
```
Notice that this is subtly different from the meaning of `var` in other
contexts: it declares an
[instance variable](https://en.wikipedia.org/wiki/Instance_variable), not just a
variable in the class's scope.
Static member variable declarations are always definitions, similar to the
current rules for global variables.
> **Open question:** Is there a way to declare class variables (scoped to the
> class, not an instance)?
**Future work:** We should add support for forward declaring static member
variables and defining them later, including in an `impl` file. But ideally we
should do that together with analogous support for global variables and using a
cohesive set of rules.
In a field declaration, an initializer (such as `= "default"` above) specifies
the default value of the field, and will be ignored if another value is supplied
for that field when constructing an instance of the class. Defaults must be
constants whose value can be determined at compile time.
#### Initializers
In both field and static member variable declarations, an initializer (such as
`= "default"` above) specifies the default or initial value. For a field, it
will be ignored if another value is supplied for that field when constructing an
instance of the class, and the default must be a constant whose value can be
determined at compile time. For a static member variable, the initializer has
the same behavior as an initializer of a global variable.
In all cases, the initializer expression is deferred and processed as-if it
appeared immediately after the end of the outermost enclosing class, similar to
[member function definitions](#deferred-member-function-definitions).
**Open question:** For a generic class, we will need to decide the rules for
whether a static member variable's initializer is evaluated and storage created
for it. That could occur for any specific of the class we monomorphize? Or only
specifics of the static member variable that are referenced? Answering this is
left as future work.
#### Syntax
The pattern in a field declaration must be a run-time binding pattern, so the
full syntax is:
@@ -727,6 +768,13 @@ full syntax is:
_field-declaration_ ::= `var` _identifier_ `:` _expression_ [ `=` _expression_
] `;`
Static member variable declarations provide the more general
[_variable pattern_ syntax](values.md#binding-patterns-and-local-variables-with-let-and-var)
based on the [`var` pattern modifier](pattern_matching.md#var):
_static-member-variable-declaration_ ::= `static` `var` _pattern_ [
`=` _expression_ ] `;`
### Forward declaration
To support circular references between class types, we allow
@@ -854,64 +902,68 @@ 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.
We consider all functions declared within a class, interface, or `impl` to be
_member functions_. They are declared using `fn` and in the same way as normal
functions, but within a class body. Member functions can also be instance
_methods_ when their first explicit parameter is `self` as
[described below](#methods).
#### Class functions
Member functions are members of the type, and may be accessed using dot `.`
member access on the type. The behavior of member access on an instance depends
on whether the function is a method as described below.
A class function is like a
[C++ static member function](https://en.cppreference.com/w/cpp/language/static#Static_member_functions),
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.
Member functions may be defined lexically inline, or may be forward declared
within the class and defined after the class definition. The body of a lexically
inline member function is
[processed in a deferred manner](#deferred-member-function-definitions).
```
#### Non-methods
Member functions that aren't methods don't use any special syntax. They don't
take a `self` parameter and so are regular functions, and work similarly to
[C++ static member functions](https://en.cppreference.com/w/cpp/language/static#Static_member_functions).
A common use is for constructor functions (sometimes called factory functions).
```carbon
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};
// No `self` parameter, so it's a regular, non-instance function.
fn Origin() -> Self {
return {.x = 0, .y = 0};
}
}
```
Class functions are members of the type, and may be accessed as using dot `.`
member access either the type or any instance.
Member access naming these functions on an instance of a type behaves the same
as member access on the type itself.
```
var p1: Point = Point.Origin();
var p2: Point = p1.CreateCentered();
```carbon
var a: Point = Point.CreateCentered();
// OK, `a` is evaluated and its value is discarded.
var b: Point = a.CreateCentered();
```
#### Methods
[Method](<https://en.wikipedia.org/wiki/Method_(computer_programming)>)
declarations are distinguished from [class function](#class-functions)
declarations by having a `self` 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
`self` parameter. Methods may be written lexically inline or after the class
declaration.
declarations are member functions distinguished by having a `self` parameter as
the first parameter in the explicit parameter list using parens `(`...`)`. The
type in the binding syntax for the `self` parameter, typically `: Self`, is
optional and if omitted defaults to `Self`. There is no implicit member access
in methods, so inside the method body members are accessed through the `self`
parameter.
```carbon
class Circle {
fn Diameter[self: Self]() -> f32 {
fn Diameter(self) -> f32 {
return self.radius * 2;
}
fn Expand[ref self: Self](distance: f32);
fn Expand(ref self, distance: f32);
var center: Point;
var radius: f32;
}
fn Circle.Expand[ref self: Self](distance: f32) {
fn Circle.Expand(ref self, distance: f32) {
self.radius += distance;
}
@@ -924,20 +976,31 @@ Assert(Math.Abs(c.Diameter() - 4.0) < 0.001);
- Methods are called 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 `[self: Self]` in the method
the `Circle` instance. This is signified using `self` in the method
declaration.
- `c.Expand(`...`)` does modify the value of `c`. This is signified using
`[ref self: Self]` in the method declaration.
`ref self` in the method declaration.
The pattern '`ref self:` _type_' means "the argument must be a
[reference expression](/docs/design/values.md#reference-expressions), and must
match the pattern '`self:` _type_'".
The pattern `ref self` means "the argument must be a
[reference expression](/docs/design/values.md#reference-expressions)".
Because methods are modeled as functions with an explicit `self` parameter, they
can also be called directly by way of the type name without using dot-notation:
```carbon
var d: f32 = Circle.Diameter(c);
```
If the method declaration also includes
[deduced compile-time parameters](/docs/design/generics/overview.md#deduced-parameters),
the `self` parameter must be in the same list in square brackets `[`...`]`. The
`self` parameter may appear in any position in that list, as long as it appears
after any names needed to describe its type.
they appear in square brackets `[`...`]` as usual, while `self` remains the
first parameter in the parens `(`...`)`:
```carbon
class Wrapper(T:! type) {
fn Print[U:! type](self, x: U);
}
```
#### Deferred member function definitions
@@ -1197,18 +1260,16 @@ A base class may define
methods whose implementation may be overridden in a derived class.
Only methods defined in the scope of the class definition may be virtual, not
any defined in
[out-of-line interface `impl` declarations](/docs/design/generics/details.md#out-of-line-impl).
Interface methods may be implemented using virtual methods when the
[impl is inline](/docs/design/generics/details.md#inline-impl), and calls to
those methods by way of the interface will do virtual dispatch just like a
direct call to the method does.
[Class functions](#class-functions) may not be declared virtual. Neither may
functions with [compile-time parameters](/docs/design/generics/overview.md),
whether those are `template` or checked, explicit or deduced. Compile-time
parameters on the enclosing scope are allowed, though, so generic classes may
have virtual methods.
Functions with [compile-time parameters](/docs/design/generics/overview.md) may
not be virtual, whether those are `template` or checked, explicit or deduced.
Compile-time parameters on the enclosing scope are allowed, though, so generic
classes may have virtual methods.
##### Virtual modifier keywords
@@ -2004,12 +2065,12 @@ different type than `DynPtr(MyInterface)` since the receiver input to the
function members of the vtable for the former does not match those in the
witness table for the latter.
#### Overloaded methods
#### Overloaded member functions
We allow a derived class to define a [class function](#class-functions) with the
same name as a class function in the base class. For example, we expect it to be
pretty common to have a constructor function named `Create` at all levels of the
type hierarchy.
We allow a derived class to define a member function with the same name as a
member function in the base class when neither are methods. For example, we
expect it to be common to have a constructor function named `Make` at all levels
of the type hierarchy.
Beyond that, we may want some rules or restrictions about defining methods in a
derived class with the same name as a base class method without overriding it.
@@ -2304,6 +2365,18 @@ the type of `U.x`."
- [#6008: Replace `impl fn` with `override fn`](https://github.com/carbon-language/carbon-lang/pull/6008)
- [#7016: Updating `self` syntax and adding `static` fields](https://github.com/carbon-language/carbon-lang/pull/7016)
- [Don't put `self` in either parameter list](/proposals/p7016.md#dont-put-self-in-either-parameter-list)
- [`self` syntax in the deduced parameter list `[]`](/proposals/p7016.md#self-syntax-in-the-deduced-parameter-list-)
- [`class` modifier for non-instance member variables and functions](/proposals/p7016.md#class-modifier-for-non-instance-member-variables-and-functions)
- [Alternative keywords for non-instance member variables](/proposals/p7016.md#alternative-keywords-for-non-instance-member-variables)
- [`shared`](/proposals/p7016.md#shared)
- [`global`](/proposals/p7016.md#global)
- [`static` for non-instance member functions](/proposals/p7016.md#static-for-non-instance-member-functions)
- [`static` for package- and namespace-scope variables](/proposals/p7016.md#static-for-package--and-namespace-scope-variables)
- [Distinct `method` introducer](/proposals/p7016.md#distinct-method-introducer)
## References
- [#257: Initialization of memory and variables](https://github.com/carbon-language/carbon-lang/pull/257)
@@ -2317,3 +2390,4 @@ the type of `U.x`."
- [#2287: Allow unqualified name lookup for class members](https://github.com/carbon-language/carbon-lang/pull/2287)
- [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760)
- [#5017: Destructor syntax](https://github.com/carbon-language/carbon-lang/pull/5017)
- [#7016: Updating `self` syntax and adding `static` fields](https://github.com/carbon-language/carbon-lang/pull/7016)
+2 -2
View File
@@ -165,8 +165,8 @@ Other designs build upon basic function syntax to add advanced features:
- [Generic functions](generics/overview.md#generic-functions) adds support for
deduced parameters and compile-time parameters.
- [Class member functions](classes.md#member-functions) adds support for
methods and class functions.
- [Member functions](classes.md#member-functions) adds support for methods and
non-instance member functions.
## Alternatives considered
+10 -9
View File
@@ -45,7 +45,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Use case: Accessing interface names](#use-case-accessing-interface-names)
- [Future work: Adapter with stricter invariants](#future-work-adapter-with-stricter-invariants)
- [Associated constants](#associated-constants)
- [Associated class functions](#associated-class-functions)
- [Associated functions](#associated-functions)
- [Associated facets](#associated-facets)
- [Parameterized interfaces](#parameterized-interfaces)
- [Parameterized named constraints](#parameterized-named-constraints)
@@ -2236,11 +2236,16 @@ fn ExtractPoint[PointT:! NSpacePoint](
**Aside:** The use of `:!` here means these `let` declarations will only have
compile-time and not runtime storage associated with them.
### Associated class functions
### Associated functions
To be consistent with normal
[class function](/docs/design/classes.md#class-functions) declaration syntax,
associated class functions are written using a `fn` declaration:
Associated constants can also be _functions_. These are called _associated
functions_, and include functions that are
[methods](/docs/design/classes.md#methods).
To be consistent with
[class member function](/docs/design/classes.md#member-functions) declaration
syntax, associated functions are written using a `fn` declaration within the
`interface` definition:
```carbon
interface DeserializeFromString {
@@ -2268,10 +2273,6 @@ var y: MySerializableType = Deserialize(MySerializableType, "4");
This is instead of declaring an associated constant using `let` with a function
type.
Together associated methods and associated class functions are called
_associated functions_, much like together methods and class functions are
called [member functions](/docs/design/classes.md#member-functions).
> **TODO:** Document rules on where associated function implementations can be
> declared, as adopted in
> [p5168: Forward `impl` declaration of an incomplete interface](/proposals/p5168.md).
+2 -2
View File
@@ -434,8 +434,8 @@ _associated constant_. If the type of the associated constant is a
which corresponds to what is called an "associated type" in other languages
([Swift](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Associated-Types),
[Rust](https://doc.rust-lang.org/reference/items/associated-items.html#associated-types)).
Similarly, an interface can have _associated function_, _associated method_, or
_associated class function_.
Similarly, an interface can have an _associated function_ (either an instance
method or a non-instance function).
Different types can satisfy an interface with different definitions for a given
member. These definitions are _associated_ with what type is implementing the
@@ -312,9 +312,9 @@ interoperability will include functions, primitive types, and structs that only
contain member variables.
Features where interoperability will rely on more advanced C++-specific
features, such as templates, inheritance, and class functions, need not be
supported for C. These would require a C-specific interoperability model that
will not be included.
features, such as templates, inheritance, and non-instance member functions,
need not be supported for C. These would require a C-specific interoperability
model that will not be included.
## Non-goals
+11 -8
View File
@@ -131,7 +131,8 @@ would, in the cases where they overlap.
A name binding pattern is a pattern.
- _binding-pattern_ ::= `ref`? (_identifier_ | `self`) `:` _expression_
- _binding-pattern_ ::= `ref`? (_identifier_ `:` _expression_ | `self` (`:`
_expression_)?)
- _binding-pattern_ ::= `template`? _identifier_ `:!` _expression_
- _pattern_ ::= _binding-pattern_
@@ -193,13 +194,15 @@ fn F() -> i32 {
}
```
When `self` is used instead of an identifier, the pattern must appear in the
implicit parameter list of a method (as discussed [here](classes.md#methods)).
During pattern matching in a method call, the parameter pattern containing
`self` is matched with the object that the method was invoked on. In all other
respects, the `self` pattern behaves just like an ordinary binding pattern,
introducing a binding named `self` into scope, just as if `self` were an
identifier rather than a keyword.
When `self` is used instead of an identifier, the pattern must appear as the
first parameter in the explicit parameter list of a method, optionally nested
within a `var` pattern, as discussed [here](classes.md#methods). If the "`:`
_expression_" is omitted, it defaults to `Self`. During pattern matching in a
method call, the parameter pattern containing `self` is matched with the object
that the method was invoked on, and the call arguments are matched against the
subsequent parameters. In all other respects, the `self` pattern behaves just
like an ordinary binding pattern, introducing a binding named `self` into scope,
just as if `self` were an identifier rather than a keyword.
#### Anonymous bindings
+390
View File
@@ -0,0 +1,390 @@
# Updating `self` syntax and adding `static` member variables
<!--
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/7016)
<!-- toc -->
## Table of contents
- [Abstract](#abstract)
- [Problem](#problem)
- [Background](#background)
- [Proposal](#proposal)
- [Details](#details)
- [Member variables](#member-variables)
- [Member functions](#member-functions)
- [Calling without method syntax](#calling-without-method-syntax)
- [Rationale](#rationale)
- [Alternatives considered](#alternatives-considered)
- [Don't put `self` in either parameter list](#dont-put-self-in-either-parameter-list)
- [`self` syntax in the deduced parameter list `[]`](#self-syntax-in-the-deduced-parameter-list-)
- [`class` modifier for non-instance member variables and functions](#class-modifier-for-non-instance-member-variables-and-functions)
- [Alternative keywords for non-instance member variables](#alternative-keywords-for-non-instance-member-variables)
- [`shared`](#shared)
- [`global`](#global)
- [`static` for non-instance member functions](#static-for-non-instance-member-functions)
- [`static` for package- and namespace-scope variables](#static-for-package--and-namespace-scope-variables)
- [Distinct `method` introducer](#distinct-method-introducer)
<!-- tocstop -->
## Abstract
Update the syntax for class (and interface/`impl`) methods to move `self` into
the parameter parentheses `()` and make the type in its binding optional
(defaulting to `Self`). Introduce the `static` keyword for non-instance member
variables to indicate static storage. Reflects the decision in leads issue
[#6931](https://github.com/carbon-language/carbon-lang/issues/6931).
## Problem
The placement of `self` in the parameter list has been a repeated source of
debate around the syntax design of Carbon -- grouping it with deduced parameters
like types is surprising for some readers and nearly unprecedented in modern
programming languages. As this is an especially pervasive and impactful aspect
of Carbon's syntax given the prevalence of methods, it is important to revisit
the syntax here and make a durable decision on how to approach this part of
Carbon's syntax.
There is also a problem that we don't have a syntax for representing
_non_-instance data members. Especially as we look at more C++ interop and more
migrations from Carbon to C++ it is important to have a clear and idiomatic
syntax for these constructs.
Key questions this proposal should address include:
- Where to place `self` (in `[]` versus `()`).
- How to distinguish instance methods from non-instance functions.
- How to declare non-instance member variables without overloading keywords or
creating confusion in interfaces vs classes.
## Background
Leads discussed various syntax options for `self` placement and non-instance
members in issue
[#6931](https://github.com/carbon-language/carbon-lang/issues/6931).
## Proposal
We propose the following updates to the syntax for class members:
1. **Move `self` to the `()` parameter list**: Instance methods declare `self`
as the first parameter in the explicit parameter list. This aligns methods
with a model where method calls are conceptually sugar for function calls
with an explicit object argument.
2. **Make `self` type optional**: Optionally allow omitting the type of `self`,
which results in the type being exactly `Self`. Example:
`fn MyMethod(self)`.
3. **Use `static` for non-instance data members**: Use `static var` for data
members that are part of the type rather than part of instances of the type.
4. **No keyword for non-instance member functions**: Functions without a `self`
parameter declared within a `class` are considered non-instance member
functions without any extra syntax.
Treating methods as functions with an explicit `self` parameter means they can
be called using standard function call syntax (for example,
`MyClass.Method(obj)`), not just method call syntax (`obj.Method()`).
While methods can be invoked as regular functions, method syntax (`obj.Method`)
continues to form a "bound member function" that adapts `obj`. This allows the
call to be resolved appropriately even when `obj` requires conversion to match
the type of `self`. Similarly, we expect `obj.(Class.Method)(args)` to follow
the same model. Fundamentally, our goal is that this does not change the
[instance binding model](/docs/design/expressions/member_access.md#instance-binding).
## Details
### Member variables
_Member variables_ are any `var`s declared within a class context (or in the
future, potentially an interface or `impl` context).
Instance member variables, or _fields_, are unchanged in the current design.
This proposal adds support for non-instance member variables, or _static member
variables_, using the `static` modifier keyword on the `var` declaration. Static
member variables are associated with the type itself rather than instances of
the type, and have static storage duration.
```carbon
class Widget {
// Non-instance member variable.
static var count: i32;
// Non-instance member function.
fn ResetCount() { count = 0; }
}
```
This does not apply to package- and namespace-scope variables (if we support
them) because static storage duration is the default in those contexts. It is
currently limited to classes, but if we allow variables with static storage
duration in other contexts that don't have a strong default of that storage,
like interfaces and `impl` declarations, they should use the same syntax.
### Member functions
We propose retiring the terminology "class function". It is confusing when used
in contexts like an interface (which is not a class) or where "class" implies
other semantics. Instead, we consider all functions declared within a class,
interface, or `impl` to be _member functions_. Member functions are either
_methods_ (taking a `self` parameter) or _non-instance member functions_.
In practice, we most often are referring to member functions broadly, regardless
of whether they are methods or not, or referring very specifically to methods.
Because referring to the broad set happens much more than referring narrowly to
non-instance member functions, we prioritize terminology that builds on a simple
broad term. We also don't expect this to compound with other adjectives like
"associated" as those will already imply "member", and so would be _associated
functions_ for the broad group and _associated methods_ for the common narrow
group.
Non-instance member functions don't use any special syntax. They don't take a
`self` parameter and so are regular functions. For example:
```carbon
class Point {
var x: i32;
var y: i32;
// No `self` parameter, so it's a regular, non-instance function.
fn Create() -> Self {
return {.x = 0, .y = 0};
}
}
fn F(p: Point) {
var p2: Point = Point.Create();
}
```
Instance member functions, or _methods_, declare `self` as the first parameter
in the `()` list. Specifying the type of `self` is optional and defaults to
`: Self`.
```carbon
class Point {
var x: i32;
var y: i32;
// Explicit type in the `self` binding.
fn GetX(self: Point) -> i32 { return self.x; }
// Omitted type in the `self` binding (defaults to Point).
fn GetY(self) -> i32 { return self.y; }
// By-reference `self` binding.
fn SetX(ref self, new_x: i32) { self.x = new_x; }
}
```
Moving `self` into the parentheses reinforces the conceptual model that method
calls are sugar for function calls with an explicit object argument.
Specifically, `obj.Method(args...)` is equivalent to
`Type.Method(obj, args...)`.
This adopts a **partial-application model** which unifies methods and functions.
A function is an "instance method" if and only if its first parameter is named
`self`. It can always be called directly with the first argument passed to the
`self` parameter. When `Method` names a function with a `self` parameter, the
syntax `obj.Method` is just syntactic sugar for partially applying the `obj` to
the `self` parameter, which can then be called as a function by passing
arguments for the remaining parameters.
For non-instance member functions, the absence of a `self` parameter is
sufficient to distinguish them from methods.
#### Calling without method syntax
Because methods are modeled as functions with an explicit `self` parameter, they
can be called as ordinary functions using their qualified names, with the `self`
argument as the first element of the explicit parameter list:
```carbon
fn F(p: Point) {
// Standard method call syntax:
var x1: i32 = p.GetX();
// Called as an ordinary function:
var x2: i32 = Point.GetX(p);
}
```
## Rationale
This proposal effectively advances Carbon's goals by focusing on:
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write):
Moving `self` to the explicit parameter list within parentheses aligns
Carbon's syntax with essentially all modern programming languages that use
an explicit object parameter, notably including C++23 itself, Python, and
Rust. It also reduces the visual cost of non-generic methods by removing the
need for an additional delimiter kind in their declaration. Making the
`: Self` part optional further reduces clutter in the most common cases and
matches the syntax used in other languages like Rust.
- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution):
Focusing `static` on storage (member variables) provides a crisp definition
that works unambiguously across classes, interfaces, and `impl` blocks.
## Alternatives considered
### Don't put `self` in either parameter list
We considered two approaches to modeling `self` that separated it from
parameters of any kind, implicit or explicit.
1. We could place `self` in an independent position with a different set of
delimiters such as:
```carbon
class Point {
var x: i32;
var y: i32;
// `self` as a separate component, likely in between the implicit and
// explicit parameter lists.
fn Create[T:! type]<self>(x: T) -> Self {
return {.x = x as i32, .y = 0};
}
}
```
2. We could make `self` implicit, similar to C++.
We have generally been happy with `self` being explicit instead of implicit and
so to an extent we didn't deeply consider (2) as that wasn't part of the problem
we set out to solve. We're still comfortable with the rationale about this
aspect of the syntax from [p0722](/proposals/p0722.md#full-receiver-type).
We did consider (1) but were unable to find a syntax that felt compelling.
Taking a new balanced delimiter is an especially difficult and expensive choice
for the syntax. And without that, most syntaxes felt verbose for something that
we expect to be extremely common and a natural "default".
### `self` syntax in the deduced parameter list `[]`
We considered placing `self` in the deduced parameter list (for example,
`fn F[self: Self](...)`), as this has been the historical syntax in Carbon.
However, users strongly associate the implicit parameter list in `[]`s with
deduced, compile-time parameters, but the `self` parameter is a _runtime_
argument passed explicitly by the caller. While we have syntactic distinctions
such as the `!` and the `self` keyword, the contextual collision still adds some
cognitive load for some readers. We do imagine potential runtime implicit
parameters in the future, but these are expected to be rare enough to be easier
to accommodate and to leverage distinguishing syntax.
In contrast to these challenges, putting `self` in the explicit parameter list
`()` aligns with the semantics of `self` as being an otherwise-normal parameter.
Last but not least, most other languages with explicit object parameters put
them at the start of the parameter list inside `()`s (see for example Python and
Rust). Being consistent with how object parameters are modeled in other
languages is expected to reduce confusion and surprise for readers of Carbon.
This is especially nice here, as we're asking C++ programmers to move from
_implicit_ object parameters to _explicit_ object parameters -- aligning with
other languages' explicit object parameter syntax hopefully minimizes that cost.
There was some concern that removing `self` from the `[]`s would result in
readers assuming that the `[]`s can _only_ contain type parameters, similar to
how Python works. However, that concern didn't carry as much weight as the other
considerations for the leads.
Ultimately, the leads preferred the placement in the `()`s and the associated
tradeoffs with that syntax.
### `class` modifier for non-instance member variables and functions
We considered using `class var` and `class fn` to mark non-instance member
variables and functions. However, `class` is already an introducer in Carbon.
Using it as a modifier adds an overloaded meaning. This meaning would also
diverge from usage in other languages. For example, Swift uses `class` versus
`static` to distinguish between class and struct methods, and has complex
virtual dispatch rules that aren't part of the Carbon design. These members also
appear in interfaces and `impl` blocks where the `class` keyword would be mildly
surprising.
### Alternative keywords for non-instance member variables
The decision to use `static` at all in Carbon is relatively controversial. It
has a history in C++ of being used for a sprawling and seemingly ever-growing
set of things, in addition to the already subtle meanings inherited from C.
We chose `static` for non-instance member variables because of widely Carbon's
major peer languages (notably C++, Rust, Swift, and Java) use the keyword
`static` to mark class member variables with static storage duration. Reusing
this spelling avoids a surprising divergence from the languages our users are
most likely to be familiar with, and provides a clear, specific meaning focused
on storage duration. This aligns with common definitions of
[static variables](https://en.wikipedia.org/wiki/Static_variable) focusing on
lifetime contrasted with scope.
A key aspect of the leads being comfortable with this direction was having the
storage implication of the keyword be fundamental to _any_ usage of it rather
than diluting that meaning with uses in other contexts.
Given that the `static` keyword did come with all of these concerns, we
considered a number of alternatives and the specific rationale for not selecting
those is recorded here.
#### `shared`
We considered using `shared var` to mark non-instance member variables. However,
this term is heavily associated with concurrency and thread safety (for example,
shared ownership, shared pointers). Reserving it for class members would collide
with its use for future concurrency features (see
[concurrency control design](https://docs.google.com/document/d/1WVWcmJdVBlapza_kPj2l3mOO-yw_hNXpb2u-Ren-I5M/)
and
[shared memory in CUDA](https://developer.nvidia.com/blog/using-shared-memory-cuda-cc/)).
The risk of confusion outweighed the benefits of identifying member variables
that are conceptually shared across instances.
#### `global`
We considered using `global var` to mark non-instance member variables. However,
`global` is most widely used to refer to _scope_ (global scope), whereas these
members are explicitly scoped to a type (or potentially an interface). The most
common distinction is that of "global" implicating scope or access, while
"static" more consistently describes the storage model.
### `static` for non-instance member functions
We considered applying `static` to both member variables and functions (for
example, `static fn F()`), for consistency with C++, Java, C#, and Swift.
However, functions do not have instances or storage in the same way data members
do. The absence of a `self` parameter is sufficient to distinguish instance
methods from non-instance functions, making the keyword redundant for functions.
Reusing `static` here would stretch or dilute its meaning in Carbon. Not using
it lets us keep the meaning narrow, and very specific to storage. It also helps
emphasize the underlying unification that we have for member functions but not
for data members: methods _are_ member functions, and can even be called as such
when a viable object is explicitly passed to the `self` parameter. The only
thing special about methods is that they _additionally_ allow the method call
syntax.
### `static` for package- and namespace-scope variables
We considered whether to require or allow the `static` keyword on package- and
namespace-scope variables (if we support them).
The storage of these variables should be described as "static" the same way as
it is for static member variables. However, we propose omitting the keyword in
those contexts because static storage is the strong default. In contrast, the
strong default for class members is instance storage, so a keyword is needed to
opt out of that default.
### Distinct `method` introducer
We considered using a distinct introducer like `method F()` instead of
`fn F(self)`. However, we prefer a uniform `fn` introducer for all functions,
relying on the presence of the `self` parameter to identify methods. This
provides greater consistency across all different kinds of functions, including
lambdas. It also embraces the unification of methods and functions.