# Nominal classes and methods [Pull request](https://github.com/carbon-language/carbon-lang/pull/722) ## 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) ## Problem We need facilities for defining new nominal [record types]() 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 brackets `[`...`]`. 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 simplifies 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++](). 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.