Clarify rules around Self and .Self
Table of contents
Abstract
A number of smaller changes grouped together in one proposal:
- Make
Selfa keyword. - Clarify that
Selfrefers to the current type in a base class and in impl declarations. - Clarify when
.Selfis legal, and what type it has. - Also specify that
whereis not an associative operator.
Problem
There were a number of gaps found in the design when @zygoloid went to implement these features in the explorer codebase, for example #1311: Basic support for .Self within :! bindings and where expressions.
Background
Self was introduced for interfaces and implementations in #524: Generics overview and #553: Generics details part 1. Self was introduced for class types and methods in #494: Method syntax and #722: Nominal classes and methods.
Constraints using where and .Self were introduced in #818: Constraints for generics (generics details 3). The use of where to set associated types was introduced in #1013: Generics: Set associated constants using where constraints.
The type of .Self and where it would be introduced grammatically was discussed #generics channel on Discord on 2022-06-07.
Proposal
This proposal implements a number of changes and clarifications about the use of Self and .Self with generics:
Selfis now a keyword, and may not be used as an identifier even in contexts whereSelfhas no meaning. IfSelfis used in a C++ API, Carbon will use the same mechanism for interop as other Carbon keywords.- Clarify that
Selfalways refers to the current type, even for virtual functions implemented in a derived class, not the type implementing the method. Selfin animpldeclaration may be used after theasto refer to the type being implemented. This could be the type named beforeaswhen specified, otherwise it is the enclosing type.- Clarify that
.Selfis legal after:!andwhere, as long as it only refers to a single type variable. - Specify the type of
.SelfasTypeafter:!, orMyConstraintafterMyConstraint where.
In addition, this proposal specifies that where is not an associative operator.
Details
Self was added as a keyword to docs/design/lexical_conventions/words.md. The other rules were added to docs/design/classes.md and docs/design/generics/details.md.
Rationale
This proposal fills in gaps with an aim to make things consistent and simplify by having fewer rules where possible. For example, the rule saying that it is an error if .Self could mean two different things is consistent with other name lookup rules, such as those from #989: Member access expressions and #2070: Always == not = in where clauses. Simplicity benefits Carbon’s language tools and ecosystem and consistency leads to code that is easy to read, understand, and write.
Alternatives considered
Self not a keyword
An alternative considered was forbidding identifiers to be equal to Self. The big concern with that approach is that we would need some other way to interoperate with C++ code, particularly classes, that had a Self member. If we were adding Self to the language later as part of evolution, we would make it a keyword. That would allow us to use the same evolution strategy as other keywords – we would automatically update the code to change existing uses of Self to the raw identifier syntax.
Note that at this time no raw identifier syntax has been approved, but Rust uses a r# prefix. If Carbon used the same syntax, existing uses of Self would be changed to r#Self, and so r#Self should still be a legal identifier.
Make Self a member of all types
We considered making Self a member of all types. From this uses of Self and .Self would follow naturally. It would have other consequences as well:
T.Self == Tfor all typesT.x.Self, wherexis a non-type value with typeT, would beT. This is because under the normal member-access rules, sinceSelfis not a member ofx, it would look inTand findT.Self.
This raised the question of whether Self is a member of type-of-types like Type. That would seem to introduce an ambiguity for i32.Self. Furthermore, using x.Self to get the type of x seemed tricky, it would be better to have something that used the word “type” to do that.
Since Self is a keyword, we don’t need to make it follow the normal member-access rules. So we instead only define what it means in places where we have a use case.
This was discussed on 2022-08-29 in the #typesystem channel in Discord.
where operator could be associative
We considered making the where operator associative, since an expression like
Interface1 where .AssocType1 is Interface2 where .AssocType2 == i32
would more usefully be interpreted as:
Interface1 where .AssocType1 is (Interface2 where .AssocType2 == i32)
than the alternative. However, this is expected to be a rare case and so it seemed much friendlier to humans to require parentheses or a separate named constraint declaration. This way they can easily visually disambiguate how it is interpreted without having to remember a rule that won’t commonly be relevant.
This was discussed in the #syntax channel on Discord on 2022-05-27 and the weekly sync meeting on 2022-06-01.