From 10b2b7184747cc55f14b3a1b5256726542cf91e4 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Tue, 12 May 2026 18:30:44 -0700 Subject: [PATCH] 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 Co-authored-by: Richard Smith --- docs/design/README.md | 51 +-- docs/design/classes.md | 214 ++++++---- docs/design/functions.md | 4 +- docs/design/generics/details.md | 19 +- docs/design/generics/terminology.md | 4 +- .../interoperability/philosophy_and_goals.md | 6 +- docs/design/pattern_matching.md | 19 +- proposals/p7016.md | 390 ++++++++++++++++++ 8 files changed, 588 insertions(+), 119 deletions(-) create mode 100644 proposals/p7016.md diff --git a/docs/design/README.md b/docs/design/README.md index 83858511afa2..995fa62197e2 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -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_](). 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++](), 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]() 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 diff --git a/docs/design/classes.md b/docs/design/classes.md index fd47af15ccb6..4a9e5a00f759 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -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]() -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) diff --git a/docs/design/functions.md b/docs/design/functions.md index cc1dc78569fb..4bc2e9d59a29 100644 --- a/docs/design/functions.md +++ b/docs/design/functions.md @@ -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 diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 7b8c1a38127a..7b88f69d99a5 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -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). diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 2efeee461fdb..e79ca14cecaf 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -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 diff --git a/docs/design/interoperability/philosophy_and_goals.md b/docs/design/interoperability/philosophy_and_goals.md index 9af09f0f1b24..788e08919e81 100644 --- a/docs/design/interoperability/philosophy_and_goals.md +++ b/docs/design/interoperability/philosophy_and_goals.md @@ -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 diff --git a/docs/design/pattern_matching.md b/docs/design/pattern_matching.md index b1c442f8d2a0..09f0388b8244 100644 --- a/docs/design/pattern_matching.md +++ b/docs/design/pattern_matching.md @@ -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 diff --git a/proposals/p7016.md b/proposals/p7016.md new file mode 100644 index 000000000000..cab879af5293 --- /dev/null +++ b/proposals/p7016.md @@ -0,0 +1,390 @@ +# Updating `self` syntax and adding `static` member variables + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/7016) + + + +## 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) + + + +## 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](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.