A number of smaller changes grouped together in one proposal:
- Make
Self
a keyword. - Clarify that
Self
refers to the current type in a base class and in impl declarations. - Clarify when
.Self
is legal, and what type it has. - Also specify that
where
is not an associative operator.
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.
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.
This proposal implements a number of changes and clarifications about the use of
Self
and .Self
with generics:
Self
is now a keyword, and may not be used as an identifier even in contexts whereSelf
has no meaning. IfSelf
is used in a C++ API, Carbon will use the same mechanism for interop as other Carbon keywords.- Clarify that
Self
always refers to the current type, even for virtual functions implemented in a derived class, not the type implementing the method. Self
in animpl
declaration may be used after theas
to refer to the type being implemented. This could be the type named beforeas
when specified, otherwise it is the enclosing type.- Clarify that
.Self
is legal after:!
andwhere
, as long as it only refers to a single type variable. - Specify the type of
.Self
asType
after:!
, orMyConstraint
afterMyConstraint where
.
In addition, this proposal specifies that where
is not an associative
operator.
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
.
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.
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.
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 == T
for all typesT
.x.Self
, wherex
is a non-type value with typeT
, would beT
. This is because under the normal member-access rules, sinceSelf
is not a member ofx
, it would look inT
and 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.
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.