diff --git a/docs/design/README.md b/docs/design/README.md index 433bd352feb1b..78b9799aa6d1a 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -42,7 +42,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Pointers and references](#pointers-and-references) - [Arrays and slices](#arrays-and-slices) - [User-defined types](#user-defined-types) - - [Structs](#structs) + - [Classes](#classes) - [Allocation, construction, and destruction](#allocation-construction-and-destruction) - [Assignment, copying, and moving](#assignment-copying-and-moving) - [Comparison](#comparison) @@ -170,14 +170,14 @@ cleaned up during evolution. Name paths in Carbon always start with the package name. Additional namespaces may be specified as desired. -For example, this code declares a struct `Geometry.Shapes.Flat.Circle` in a +For example, this code declares a class `Geometry.Shapes.Flat.Circle` in a library `Geometry/OneSide`: ```carbon package Geometry library("OneSide") namespace Shapes; namespace Flat; -struct Flat.Circle { ... } +class Flat.Circle { ... } ``` This type can be used from another package: @@ -487,7 +487,7 @@ fn Sum(a: Int, b: Int) -> Int { ## Types > References: [Primitive types](primitive_types.md), [tuples](tuples.md), and -> [structs](structs.md) +> [classes](classes.md) > > **TODO:** References need to be evolved. @@ -595,19 +595,17 @@ fn RemoveLast(x: (Int, Int, Int)) -> (Int, Int) { ### User-defined types -#### Structs +#### Classes -> References: [Structs](structs.md) -> -> **TODO:** References need to be evolved. +> References: [Classes](classes.md) -`struct`s are a way for users to define their own data strutures or named -product types. +Classes are a way for users to define their own data strutures or named product +types. For example: ```carbon -struct Widget { +class Widget { var x: Int; var y: Int; var z: Int; @@ -622,50 +620,26 @@ Breaking apart `Widget`: - `Widget` has one `String` member: `payload`. - Given an instance `dial`, a member can be referenced with `dial.paylod`. -More advanced `struct`s may be created: +##### Allocation, construction, and destruction -```carbon -struct AdvancedWidget { - // Do a thing! - fn DoSomething(self: AdvancedWidget, x: Int, y: Int); +> **TODO:** Needs a feature design and a high level summary provided inline. - // A nested type. - struct Nestedtype { - // ... - } +##### Assignment, copying, and moving - private var x: Int; - private var y: Int; -} +You may use a _structural data class literal_, also known as a _struct literal_, +to assign or initialize a variable with a class type. -fn Foo(thing: AdvancedWidget) { - thing.DoSomething(1, 2); -} +```carbon +var sprocket: Widget = {.x = 3, .y = 4, .z = 5, .payload = "Sproing"}; +sprocket = {.x = 2, .y = 1, .z = 0, .payload = "Bounce"}; ``` -Breaking apart `AdvancedWidget`: - -- `AdvancedWidget` has a public object method `DoSomething`. - - `DoSomething` explicitly indicates how the `AdvancedWidget` is passed to - it, and there is no automatic scoping - `self` must be specified as the - first input. The `self` name is also a keyword that explains how to - invoke this method on an object. - - `DoSomething` accepts `AdvancedWidget` _by value_, which is easily - expressed here along with other constraints on the object parameter. -- `AdvancedWidget` has two private data members: `x` and `y`. - - Private methods and data members are restricted to use by - `AdvancedWidget` only, providing a layer of easy validation of the most - basic interface constraints. -- `Nestedtype` is a nested type, and can be accessed as - `AdvancedWidget.Nestedtype`. - -##### Allocation, construction, and destruction - -> **TODO:** Needs a feature design and a high level summary provided inline. - -##### Assignment, copying, and moving +You may also copy one struct into another of the same type. -> **TODO:** Needs a feature design and a high level summary provided inline. +```carbon +var thingy: Widget = sprocket; +sprocket = thingy; +``` ##### Comparison @@ -818,7 +792,7 @@ be used to instantiate the parameterized definition with the provided arguments in order to produce a complete type. For example: ```carbon -struct Stack(T:$$ Type) { +class Stack(T:$$ Type) { var storage: Array(T); fn Push(value: T); diff --git a/docs/design/classes.md b/docs/design/classes.md new file mode 100644 index 0000000000000..ff915aa0a9e47 --- /dev/null +++ b/docs/design/classes.md @@ -0,0 +1,1004 @@ +# Classes + + + + + +## Table of contents + +- [Overview](#overview) +- [Use cases](#use-cases) + - [Data classes](#data-classes) + - [Encapsulated types](#encapsulated-types) + - [Without inheritance](#without-inheritance) + - [With inheritance and subtyping](#with-inheritance-and-subtyping) + - [Polymorphic types](#polymorphic-types) + - [Interface as base class](#interface-as-base-class) + - [Non-polymorphic inheritance](#non-polymorphic-inheritance) + - [Interop with C++ multiple inheritance](#interop-with-c-multiple-inheritance) + - [Mixins](#mixins) +- [Background](#background) +- [Overview](#overview-1) +- [Members](#members) + - [Data members have an order](#data-members-have-an-order) +- [Struct types](#struct-types) + - [Literals](#literals) + - [Type expression](#type-expression) + - [Assignment and initialization](#assignment-and-initialization) + - [Operations performed field-wise](#operations-performed-field-wise) +- [Future work](#future-work) + - [Nominal class types](#nominal-class-types) + - [Construction](#construction) + - [Member type](#member-type) + - [Self](#self) + - [Let](#let) + - [Methods](#methods) + - [Optional named parameters](#optional-named-parameters) + - [Field defaults for struct types](#field-defaults-for-struct-types) + - [Destructuring in pattern matching](#destructuring-in-pattern-matching) + - [Discussion](#discussion) + - [Access control](#access-control) + - [Operator overloading](#operator-overloading) + - [Inheritance](#inheritance) + - [C++ abstract base classes interoperating with object-safe interfaces](#c-abstract-base-classes-interoperating-with-object-safe-interfaces) + - [Mixins](#mixins-1) + - [Non-virtual inheritance](#non-virtual-inheritance) + - [Memory layout](#memory-layout) + - [No `static` variables](#no-static-variables) + - [Computed properties](#computed-properties) + - [Interfaces implemented for data classes](#interfaces-implemented-for-data-classes) + + + +## Overview + +A Carbon `class` is a user-defined +[record type](). This +is the primary mechanism for users to define new types in Carbon. A `class` has +members that are referenced by their names, in contrast to a +[Carbon tuple](tuples.md) which defines a +[product type](https://en.wikipedia.org/wiki/Product_type) whose members are +referenced positionally. + +Carbon supports both named, or "nominal", and unnamed, anonymous, or +"structural", class types. Nominal class types are all distinct, but structural +types are equal if they have the same sequence of member types and names. +Structural class literals may be used to initialize or assign values to nominal +class variables. + +## Use cases + +The use cases for classes include both cases motivated by C++ interop, and cases +that we expect to be included in idiomatic Carbon-only code. + +**This design currently only attempts to address the "data classes" use case.** +Addressing the other use cases is future work. + +### Data classes + +Data classes are types that consist of data fields that are publicly accessible +and directly read and manipulated by client code. They have few if any methods, +and generally are not involved in inheritance at all. + +Examples include: + +- a key and value pair returned from a `SortedMap` or `HashMap` +- a 2D point that might be used in a rendering API + +Properties: + +- Operations like copy, move, destroy, unformed, and so on are defined + field-wise. +- Anonymous classes types and literals should match data class semantics. + +Expected in idiomatic Carbon-only code. + +**Background:** Kotlin has a dedicated concise syntax for defining +[_data classes_](https://kotlinlang.org/docs/data-classes.html) that avoids +boilerplate. Python has a +[data class library](https://docs.python.org/3/library/dataclasses.html), +proposed in [PEP 557](https://www.python.org/dev/peps/pep-0557/), that fills a +similar role. + +### Encapsulated types + +There are several categories of types that support +[encapsulation](). +This is done by making their data fields private so access and modification of +values are all done through methods defined on the type. + +#### Without inheritance + +The common case for encapsulated types are those that do not participate in +inheritance. These types neither support being inherited from (they are +["final"]()) +nor do they extend other types. + +Examples of this use case include: + +- strings, containers, iterators +- types with invariants such as `Date` +- RAII types that are movable but not copyable like C++'s `std::unique_ptr` or + a file handle +- non-movable types like `Mutex` + +We expect two kinds of methods on these types: public methods defining the API +for accessing and manipulating values of the type, and private helper methods +used as an implementation detail of the public methods. + +These types are expected in idiomatic Carbon-only code. Extending this design to +support these types is future work. + +#### With inheritance and subtyping + +The [subtyping](https://en.wikipedia.org/wiki/Subtyping) you get with +inheritance is that you may assign the address of an object of a derived type to +a pointer to its base type. For this to work, the compiler needs implementation +strategies that allow operations performed through the pointer to the base type +work independent of which derived type it actually points to. These strategies +include: + +- Arranging for the the data layout of derived types to start with the data + layout of the base type as a prefix. +- Putting a pointer to a table of function pointers, a + [_vtable_](https://en.wikipedia.org/wiki/Virtual_method_table), as the first + data member of the object. This allows methods to be + [_virtual_](https://en.wikipedia.org/wiki/Virtual_function) and have a + derived-type-specific implementation, an _override_, that is used even when + invoking the method on a pointer to a base type. +- Non-virtual methods implemented on a base type should be applicable to all + derived types. In general, derived types should not attempt to overload or + override non-virtual names defined in the base type. + +Note that these subtyping implementation strategies generally rely on +encapsulation, but encapsulation is not a strict requirement in all cases. + +This subtyping relationship also creates safety concerns, which Carbon should +protect against. +[Slicing problems](https://en.wikipedia.org/wiki/Object_slicing) can arise when +the source or target of an assignment is a dereferenced pointer to the base +type. It is also incorrect to delete an object with a non-virtual destructor +through a pointer to a base type. + +##### Polymorphic types + +Carbon will fully support single-inheritance type hierarchies with polymorphic +types. + +Polymorphic types support +[dynamic dispatch](https://en.wikipedia.org/wiki/Dynamic_dispatch) using a +[vtable](https://en.wikipedia.org/wiki/Virtual_method_table), and data members, +but only single inheritance. Individual methods opt in to using dynamic +dispatch, so types will have a mix of +["virtual"](https://en.wikipedia.org/wiki/Virtual_function) and non-virtual +methods. Polymorphic types support traditional +[object-oriented single inheritance](), +a mix of [subtyping](https://en.wikipedia.org/wiki/Subtyping) and +[implementation and code reuse](). + +We exclude complex multiple inheritance schemes, virtual inheritance, and so on +from this use case. This is to avoid the complexity and overhead they bring, +particularly since the use of these features in C++ is generally discouraged. +The rule is that every type has at most one base type with data members for +subtyping purposes. Carbon will support additional base types as long as they +[don't have data members](#interface-as-base-class) or +[don't support subtyping](#mixins). + +**Background:** +[The "Nothing is Something" talk by Sandi Metz](https://www.youtube.com/watch?v=OMPfEXIlTVE) +and +[the Composition Over Inheritance Principle](https://python-patterns.guide/gang-of-four/composition-over-inheritance/) +describe design patterns to use instead of multiple inheritance to support types +that vary over multiple axes. + +In rare cases where the complex multiple inheritance schemes of C++ are truly +needed, they can be effectively approximated using a combination of these +simpler building blocks. + +Polymorphic types support a number of different kinds of methods: + +- They will have virtual methods: + - Polymorphic types will typically include virtual destructors. + - The virtual methods types may have default implementations or be + [_abstract_]() + (or + [_pure virtual_](https://en.wikipedia.org/wiki/Virtual_function#Abstract_classes_and_pure_virtual_functions)). + In the latter case, they must be implemented in any derived class that + can be instantiated. + - Virtual methods may be + [_protected_](https://en.wikipedia.org/wiki/Access_modifiers) or + [_private_](https://stackoverflow.com/questions/2170688/private-virtual-method-in-c), + intended to be called by methods in the base type but implemented in the + descendant. +- They may have non-virtual public or private helper methods, like + [encapsulated types without inheritance](#without-inheritance). These avoid + the overhead of a virtual function call, and can be written when the base + class has sufficient data members. +- They may have protected helper methods, typically non-virtual, provided by + the base type to be called by the descendant. + +Note that there are two uses for protected methods: those implemented in the +base and called in the descendant, and the other way around. +["The End Of Object Inheritance & The Beginning Of A New Modularity" talk by Augie Fackler and Nathaniel Manista](https://www.youtube.com/watch?v=3MNVP9-hglc) +discusses design patterns that split up types to reduce the number of kinds of +calls between base and derived types, and make sure calls only go in one +direction. + +We expect polymorphic types in idiomatic Carbon-only code, at least for the +medium term. Extending this design to support polymorphic types is future work. + +###### Interface as base class + +We distinguish the specific case of polymorphic base classes that have no data +members: + +- From an implementation perspective, the lack of data members removes most of + the problems with supporting multiple inheritance. +- They are about decoupling two pieces of code instead of collaborating. +- As a use case, they are used primarily for subtyping and much less + implementation reuse than other polymorphic types. +- This case overlaps with the + [interface](/docs/design/generics/terminology.md#interface) concept + introduced for [Carbon generics](/docs/design/generics/overview.md). + +Removing support for data fields greatly simplifies supporting multiple +inheritance. For example, it removes the need for a mechanism to figure out the +offset of those data fields in the object. Similarly we don't need +[C++'s virtual inheritance](https://en.wikipedia.org/wiki/Virtual_inheritance) +to avoid duplicating those fields. Some complexities still remain, such as +pointers changing values when casting to a secondary parent type, but these seem +manageable given the benefits of supporting this useful case of multiple +inheritance. + +While an interface base class is generally for providing an API that allows +decoupling two pieces of code, a polymorphic type is a collaboration between a +base and derived type to provide some functionality. This is a bit like the +difference between a library and a framework, where you might use many of the +former but only one of the latter. + +Interface base classes are primarily used for subtyping. The extent of +implementation reuse is generally limited by the lack of data members, and the +decoupling role they play is usually about defining an API as a set of public +pure-virtual methods. Compared to other polymorphic types, they more rarely have +methods with implementations (virtual or not), or have methods with restricted +access. The main use case is when there is a method that is implemented in terms +of pure-virtual methods. Those pure-virtual methods may be marked as protected +to ensure they are only called through the non-abstract API, but can still be +implemented in descendants. + +While it is typical for this case to be associated with single-level inheritance +hierarchies, there are some cases where there is an interface at the root of a +type hierarchy and polymorphic types as interior branches of the tree. The case +of generic interfaces extending or requiring other interface would also be +modeled by deeper inheritance hierarchies. + +An interface as base class needs to either have a virtual destructor or forbid +deallocation. + +There is significant overlap between interface base classes and +[Carbon interfaces](generics/overview.md#interfaces). Both represent APIs as a +collection of method names and signatures to implement. The subset of interfaces +that support dynamic dispatch are called _object-safe_, following +[Rust](https://doc.rust-lang.org/reference/items/traits.html#object-safety): + +- They don't have a `Self` in the signature of a method in a contravariant + position like a parameter. +- They don't have free associated types or other associated items used in a + method signature. + +The restrictions on object-safe interfaces match the restrictions on base class +methods. The main difference is the representation in memory. A type extending a +base class with virtual methods includes a pointer to the table of methods in +the object value itself, while a type implementing an interface would store the +pointer alongside the pointer to the value in a `DynPtr(MyInterface)`. Of +course, the interface option also allows the method table to be passed at +compile time. + +**Note:** This presumes that we include some concept of `final` methods in +interfaces to match non-virtual functions in base classes. + +We expect idiomatic Carbon-only code to generally use Carbon interfaces instead +of interface base classes. We may still support interface base classes long term +if we determine that the ability to put the pointer to the method +implementations in the object value is important for users, particularly with a +single parent as in the [polymorphic type case](#polymorphic-types). Extending +this design to support interface base classes is future work. + +**Background:** +[C++ abstract base classes](https://en.wikipedia.org/wiki/Abstract_type) that +don't have data members and +[Java interfaces]() model this +case. + +##### Non-polymorphic inheritance + +While it is not common, there are cases where C++ code uses inheritance without +dynamic dispatch or a +[vtable](https://en.wikipedia.org/wiki/Virtual_method_table). Instead, methods +are never overridden, and derived types only add data and methods. There are +some cases where this is done in C++ but would be done differently in Carbon: + +- For implementation reuse without subtyping, Carbon code should use mixins or + composition. Carbon won't support private inheritance. +- Carbon will allow data members to have size zero, so the + [empty-base optimization](https://en.cppreference.com/w/cpp/language/ebo) is + unnecessary. +- For cases where the derived type does not add any data members, in Carbon + you can potentially use adapter types instead of inheritance. + +However, there are still some cases where non-virtual inheritance makes sense. +One is a parameterized type where a prefix of the data is the same independent +of the parameter. An example of this is containers with a +[small-buffer optimization](https://akrzemi1.wordpress.com/2014/04/14/common-optimizations/#sbo), +as described in the talk +[CppCon 2016: Chandler Carruth "High Performance Code 201: Hybrid Data Structures"](https://www.youtube.com/watch?v=vElZc6zSIXM). +By moving the data and methods that don't depend on the buffer size to a base +class, we reduce the instantiation overhead for monomorphization. The base type +is also useful for reducing instantiation for consumers of the container, as +long as they only need to access methods defined in the base. + +Another case for non-virtual inheritance is for different node types within a +data structure that have some data members in common. This is done in LLVM's +map, +[red-black tree](https://github.com/llvm-mirror/libcxx/blob/master/include/__tree), +and list data structure types. In a linked list, the base type might have the +next and previous pointers, which is enough for a sentinel node, and there would +also be a derived type with the actual data member. The base type can define +operations like "splice" that only operate on the pointers not the data, and +this is in fact enforced by the type system. Only the derived node type needs to +be parameterized by the element type, saving on instantiation costs as before. + +Many of the concerns around non-polymorphic inheritance are the same as for the +non-virtual methods of [polymorphic types](#polymorphic-types). Assignment and +destruction are examples of operations that need particular care to be sure they +are only done on values of the correct type, rather than through a subtyping +relationship. This means having some extrinsic way of knowing when it is safe to +downcast before performing one of those operations, or performing them on +pointers that were never upcast to the base type. + +##### Interop with C++ multiple inheritance + +While Carbon won't support all the C++ forms of multiple inheritance, Carbon +code will still need to interoperate with C++ code that does. Of particular +concern are the `std::iostream` family of types. Most uses of those types are +the input and output variations or could be migrated to use those variations, +not the harder bidirectional cases. + +Much of the complexity of this interoperation could be alleviated by adopting +the restriction that Carbon code can't directly access the fields of a virtual +base class. In the cases where such access is needed, the workaround is to +access them through C++ functions. + +We do not expect idiomatic Carbon-only code to use multiple inheritance. +Extending this design to support interopating with C++ types using multiple +inheritance is future work. + +### Mixins + +A [mixin](https://en.wikipedia.org/wiki/Mixin) is a declaration of data, +methods, and interface implementations that can be added to another type, called +the "main type". The methods of a mixin may also use data, methods, and +interface implementations provided by the main type. Mixins are designed around +implementation reuse rather than subtyping, and so don't need to use a vtable. + +A mixin might be an implementation detail of a [data class](#data-classes), +[object type](#object-types), or +[derived type of a polymorphic type](#polymorphic-types). A mixin might +partially implement an [interface as base class](#interface-as-base-class). + +**Examples:** +[intrusive linked list](https://www.boost.org/doc/libs/1_63_0/doc/html/intrusive.html), +intrusive reference count + +In both of these examples, the mixin needs the ability to convert between a +pointer to the mixin's data (like a "next" pointer or reference count) and a +pointer to the containing object with the main type. + +Mixins are expected in idiomatic Carbon-only code. Extending this design to +support mixins is future work. + +**Background:** Mixins are typically implemented using the +[curiously recurring template pattern](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +in C++, but other languages support them directly. + +- In Dart, the mixin defines an interface that the destination type ends up + implementing, which restores a form of subtyping. See + [Dart: What are mixins?](https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3). +- Swift is considering + [a proposal to add mixin support](https://github.com/Anton3/swift-evolution/blob/mixins/proposals/NNNN-mixins.md). + +## Background + +See how other languages tackle this problem: + +- [Swift](https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html) + - has two different concepts: classes support + [inheritance](https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html) + and use + [reference counting](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html) + while structs have value semantics + - may have + [constructor functions called "initializers"](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html) + and + [destructors called "deinitializers"](https://docs.swift.org/swift-book/LanguageGuide/Deinitialization.html) + - supports + [properties](https://docs.swift.org/swift-book/LanguageGuide/Properties.html), + including computed & lazy properties + - methods are const by default + [unless marked mutating](https://docs.swift.org/swift-book/LanguageGuide/Methods.html#ID239) + - supports + [extensions](https://docs.swift.org/swift-book/LanguageGuide/Extensions.html) + - has per-field + [access control](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html) +- [Rust](https://doc.rust-lang.org/book/ch05-01-defining-structs.html) + - has no support for inheritance + - has no special constructor functions, instead has literal syntax + - has some convenience syntax for common cases: + [variable and field names matching](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#using-the-field-init-shorthand-when-variables-and-fields-have-the-same-name), + [updating a subset of fields](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax) + - [can have unnamed fields](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#using-tuple-structs-without-named-fields-to-create-different-types) + - [supports structs with size 0](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#unit-like-structs-without-any-fields) +- [Zig](https://ziglang.org/documentation/0.6.0/#struct) + - [explicitly mark structs as packed to manually control layout](https://ziglang.org/documentation/0.6.0/#packed-struct) + - has a struct literal syntax, + [including for anonymous structs](https://ziglang.org/documentation/0.6.0/#Anonymous-Struct-Literals) + - no special constructor functions + - supports fields with undefined values + - supports structs with size 0 + - supports generics by way of memoized compile time functions accepting + and returning types + - [supports default field values](https://ziglang.org/documentation/0.6.0/#toc-Default-Field-Values) + - [has no properties or operator overloading -- Zig does not like hidden control flow](https://ziglang.org/#Small-simple-language) + +## Overview + +Beyond tuples, Carbon allows defining +[record types](). This +is the primary mechanism for users to extend the Carbon type system and is +deeply rooted in C++ and its history (C and Simula). We call them _classes_ +rather than other terms as that is both familiar to existing programmers and +accurately captures their essence: they define the types of objects with +(optional) support for methods, encapsulation, and so on. + +A class type defines the interpretation of the bytes of a value of that type, +including the size, data members, and layout. It defines the operations that may +be performed on those values, including what methods may be called. A class type +may directly have constant members. The type itself is a compile-time immutable +constant value. + +## Members + +The members of a class are named, and are accessed with the `.` notation. For +example: + +``` +var p: Point2D = ...; +// Data member access +p.x = 1; +p.y = 2; +// Method call +Print(p.DistanceFromOrigin()); +``` + +[Tuples](tuples.md) are used for cases where accessing the members positionally +is more appropriate. + +### Data members have an order + +The data members of a class, or _fields_, have an order that matches the order +they are declared in. This determines the order of those fields in memory, and +the order that the fields are destroyed when a value goes out of scope or is +deallocated. + +## Struct types + +_Structural data classes_, or _struct types_, are convenient for defining +[data classes](#data-classes) in an ad-hoc manner. They would commonly be used: + +- as the return type of a function that returns multiple values and wants + those values to have names so a [tuple](tuples.md) is inappropriate +- as an initializer for other `class` variables or values +- as a type parameter to a container + +Note that struct types are examples of _data class types_ and are still classes, +but we expect later to support more ways to define data class types. Also note +that there is no `struct` keyword, "struct" is just convenient shorthand +terminology for a structural data class. + +**Future work:** We intend to support nominal [data classes](#data-classes) as +well. + +### Literals + +_Structural data class literals_, or _struct literals_, are written using this +syntax: + +``` +var kvpair: auto = {.key = "the", .value = 27}; +``` + +This produces a struct value with two fields: + +- The first field is named "`key`" and has the value `"the"`. The type of the + field is set to the type of the value, and so is `String`. +- The second field is named "`value`" and has the value `27`. The type of the + field is set to the type of the value, and so is `Int`. + +Note: A comma `,` may optionally be included after the last field: + +``` +var kvpair: auto = {.key = "the", .value = 27,}; +``` + +**Open question:** To keep the literal syntax from being ambiguous with compound +statements, Carbon will adopt some combination of: + +- looking ahead after a `{` to see if it is followed by `.name`; +- not allowing a struct literal at the beginning of a statement; +- only allowing `{` to introduce a compound statement in contexts introduced + by a keyword where they are required, like requiring `{ ... }` around the + cases of an `if...else` statement. + +### Type expression + +The type of `kvpair` in the last example would be represented by this +expression: + +``` +{.key: String, .value: Int} +``` + +This syntax is intended to parallel the literal syntax, and so uses commas (`,`) +to separate fields instead of a semicolon (`;`) terminator. This choice also +reflects the expected use inline in function signature declarations. + +Struct types may only have data members, so the type declaration is just a list +of field names and types. The result of a struct type expression is an immutable +compile-time type value. + +Note: Like with struct literal expressions, a comma `,` may optionally be +included after the last field: + +``` +{.key: String, .value: Int,} +``` + +Also note that `{}` represents both the empty struct literal and its type. + +### Assignment and initialization + +When initializing or assigning a variable with a data class such as a struct +type to a struct value on the right hand side, the order of the fields does not +have to match, just the names. + +``` +var different_order: {.x: Int, .y: Int} = {.y = 2, .x = 3}; +Assert(different_order.x == 3); +Assert(different_order.y == 2); +``` + +**Open question:** What operations and in what order happen for assignment and +initialization? + +- Is assignment just destruction followed by initialization? Is that + destruction completed for the whole object before initializing, or is it + interleaved field-by-field? +- When initializing to a literal value, is a temporary containing the literal + value constructed first or are the fields initialized directly? The latter + approach supports types that can't be moved or copied, such as mutex. +- Perhaps some operations are _not_ ordered with respect to each other? + +When initializing or assigning, the order of fields is determined from the +target on the left side of the `=`. This rule matches what we expect for classes +with encapsulation more generally. + +### Operations performed field-wise + +Generally speaking, the operations that are available on a data class value, +such as a value with a struct type, are dependent on those operations being +available for all the types of the fields. + +For example, two values of the same data class type may be compared for equality +if equality is supported for every member of the type: + +``` +var p: auto = {.x = 2, .y = 3}; +Assert(p == {.x = 2, .y = 3}); +Assert(p != {.x = 2, .y = 4}); +``` + +Similarly, a data class has an unformed state if all its members do. Treatment +of unformed state follows +[#257](https://github.com/carbon-language/carbon-lang/pull/257). + +`==` and `!=` are defined on a data class type if all its field types support +it: + +``` +Assert({.x = 2, .y = 4} != {.x = 5, .y = 3}); +``` + +**Open question:** Which other comparisons are supported is the subject of +[question-for-leads issue #710](https://github.com/carbon-language/carbon-lang/issues/710). + +``` +// Illegal +Assert({.x = 2, .y = 3} != {.y = 4, .x = 5}); +``` + +Destruction is performed field-wise in reverse order. + +Extending user-defined operations on the fields to an operation on an entire +data class is [future work](#interfaces-implemented-for-data-classes). + +## Future work + +This includes features that need to be designed, questions to answer, and a +description of the provisional syntax in use until these decisions have been +made. + +### Nominal class types + +The declarations for nominal class types will have a different format. +Provisionally we have been using something like this: + +``` +class TextLabel { + var x: Int; + var y: Int; + + var text: String; +} +``` + +It is an open question, though, how we will address the +[different use cases](#use-cases). For example, we might mark +[data classes](#data-classes) with an `impl as Data {}` line. + +### Construction + +There are a variety of options for constructing class values, we might choose to +support, including initializing from struct values: + +``` +var p1: Point2D = {.x = 1, .y = 2}; +var p2: auto = {.x = 1, .y = 2} as Point2D; +var p3: auto = Point2D{.x = 1, .y = 2}; +var p4: auto = Point2D(1, 2); +``` + +### Member type + +Additional types may be defined in the scope of a class definition. + +``` +class StringCounts { + class Node { + var key: String; + var count: Int; + } + var counts: Vector(Node); +} +``` + +The inner type is a member of the type, and is given the name +`StringCounts.Node`. + +### Self + +A `class` definition may provisionally include references to its own name in +limited ways, similar to an incomplete type. What is allowed and forbidden is an +open question. + +``` +class IntListNode { + var data: Int; + var next: IntListNode*; +} +``` + +An equivalent definition of `IntListNode`, since `Self` is an alias for the +current type, is: + +``` +class IntListNode { + var data: Int; + var next: Self*; +} +``` + +`Self` refers to the innermost type declaration: + +``` +class IntList { + class IntListNode { + var data: Int; + var next: Self*; + } + var first: IntListNode*; +} +``` + +### Let + +Other type constants can provisionally be defined using a `let` declaration: + +``` +class MyClass { + let Pi: Float32 = 3.141592653589793; + let IndexType: Type = Int; +} +``` + +There are definite questions about this syntax: + +- Should these use the `:!` generic syntax decided in + [issue #565](https://github.com/carbon-language/carbon-lang/issues/565)? +- Would we also have `alias` declarations? They would only be used for names, + not other constant values. + +### Methods + +A future proposal will incorporate +[method]() +declaration, definition, and calling into classes. The syntax for declaring +methods has been decided in +[question-for-leads issue #494](https://github.com/carbon-language/carbon-lang/issues/494). +Summarizing that issue: + +- Accessors are written: `fn Diameter[me: Self]() -> Float { ... }` +- Mutators are written: `fn Expand[addr me: Self*](distance: Float) { ... }` +- Associated functions that don't take a receiver at all, like + [C++'s static methods](), + are written: `fn Create() -> Self { ... }` + +We do not expect to have implicit member access in methods, so inside the method +body members will be accessed through the `me` parameter. + +### Optional named parameters + +Structs are being considered as a possible mechanism for implementing optional +named parameters. We have three main candidate approaches: allowing struct types +to have field defaults, having dedicated support for destructuring struct values +in pattern contexts, or having a dedicated optional named parameter syntax. + +#### Field defaults for struct types + +If struct types could have field defaults, you could write a function +declaration with all of the optional parameters in an option struct: + +``` +fn SortIntVector( + v: Vector(Int)*, + options: {.stable: Bool = false, + .descending: Bool = false} = {}) { + // Code using `options.stable` and `options.descending`. +} + +// Uses defaults of `.stable` and `.descending` equal to `false`. +SortIntVector(&v); +SortIntVector(&v, {}); +// Sets `.stable` option to `true`. +SortIntVector(&v, {.stable = true}); +// Sets `.descending` option to `true`. +SortIntVector(&v, {.descending = true}); +// Sets both `.stable` and `.descending` options to `true`. +SortIntVector(&v, {.stable = true, .descending = true}); +// Order can be different for arguments as well. +SortIntVector(&v, {.descending = true, .stable = true}); +``` + +#### Destructuring in pattern matching + +We might instead support destructuring struct patterns with defaults: + +``` +fn SortIntVector( + v: Vector(Int)*, + {stable: Bool = false, descending: Bool = false}) { + // Code using `stable` and `descending`. +} +``` + +This would allow the same syntax at the call site, but avoids +[some concerns with field defaults](https://github.com/carbon-language/carbon-lang/pull/561#discussion_r683856715) +and allows some other use cases such as destructuring return values. + +#### Discussion + +We might support destructuring directly: + +``` +var {key: String, value: Int} = ReturnKeyValue(); +``` + +or by way of a mechanism that converts a struct into a tuple: + +``` +var (key: String, value: Int) = + ReturnKeyValue().extract(.key, .value); +// or maybe: +var (key: String, value: Int) = + ReturnKeyValue()[(.key, .value)]; +``` + +Similarly we might support optional named parameters directly instead of by way +of struct types. + +Some discussion on this topic has occurred in: + +- [question-for-leads issue #505 on named parameters](https://github.com/carbon-language/carbon-lang/issues/505) +- labeled params brainstorming docs + [1](https://docs.google.com/document/d/1a1wI8SHGh3HYV8SUWPIKhg48ZW2glUlAMIIS3aec5dY/edit), + [2](https://docs.google.com/document/d/1u6GORSkcgThMAiYKOqsgALcEviEtcghGb5TTVT-U-N0/edit) +- ["match" in syntax choices doc](https://docs.google.com/document/d/1iuytei37LPg_tEd6xe-O6P_bpN7TIbEjNtFMLYW2Nno/edit#heading=h.y566d16ivoy2) + +### Access control + +We will need some way of controlling access to the members of classes. By +default, all members are fully publicly accessible, as decided in +[issue #665](https://github.com/carbon-language/carbon-lang/issues/665). + +The set of access control options Carbon will support is an open question. Swift +and C++ (especially w/ modules) provide a lot of options and a pretty wide space +to explore here. + +### Operator overloading + +This includes destructors, copy and move operations, as well as other Carbon +operators such as `+` and `/`. We expect types to implement these operations by +implementing corresponding interfaces, see +[the generics overview](generics/overview.md). + +### Inheritance + +Carbon will need ways of saying: + +- this `class` type has a virtual method table +- this `class` type extends a base type +- this `class` type is "final" and may not be extended further +- this method is "virtual" and may be overridden in descendents +- this method is "pure virtual" or "abstract" and must be overridden in + descendants +- this method overrides a method declared in a base type + +Multiple inheritance will be limited in at least a couple of ways: + +- At most one supertype may define data members. +- Carbon types can't access data members of C++ virtual base classes. + +There is a +[document considering the options for constructing objects with inheritance](https://docs.google.com/document/d/1GyrBIFyUbuLJGItmTAYUf9sqSDQjry_kjZKm5INl-84/edit). + +### C++ abstract base classes interoperating with object-safe interfaces + +We want four things so that Carbon's object-safe interfaces may interoperate +with C++ abstract base classes without data members, matching the +[interface as base class use case](#interface-as-base-class): + +- Ability to convert an object-safe interface (a type-of-type) into an + C++-compatible base class (a base type), maybe using + `AsBaseClass(MyInterface)`. +- Ability to convert a C++ base class without data members (a base type) into + an object-safe interface (a type-of-type), maybe using `AsInterface(MyIBC)`. +- Ability to convert a (thin) pointer to an abstract base class to a `DynPtr` + of the corresponding interface. +- Ability to convert `DynPtr(MyInterface)` values to a proxy type that extends + the corresponding base class `AsBaseType(MyInterface)`. + +Note that the proxy type extending `AsBaseType(MyInterface)` would be a +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. + +### Mixins + +We will need some way to declare mixins. This syntax will need a way to +distinguish defining versus requiring member variables. Methods may additionally +be given a default definition but may be overridden. Interface implementations +may only be partially provided by a mixin. Mixin methods will need to be able to +convert between pointers to the mixin type and the main type. + +Open questions include whether a mixin is its own type that is a member of the +containing type, and whether mixins are templated on the containing type. Mixins +also complicate how constructors work. + +### Non-virtual inheritance + +We need some way of addressing two safety concerns created by non-virtual +inheritance: + +- Unclear how to safely run the right destructor. +- [Object slicing](https://en.wikipedia.org/wiki/Object_slicing) is a danger + when dereferencing a pointer of a base type. + +These concerns would be resolved by distinguishing between pointers that point +to a specified type only and those that point to a type or any subtype. The +latter case would have restrictions to prevent misuse. This distinction may be +more complexity than is justified for a relatively rare use case. An alternative +approach would be to forbid destruction of non-final types without virtual +destructors, and forbid assignment of non-final types entirely. + +This open question is being considered in +[question-for-leads issue #652](https://github.com/carbon-language/carbon-lang/issues/652). + +### Memory layout + +Carbon will need some way for users to specify the memory layout of class types +beyond simple ordering of fields, such as controlling the packing and alignment +for the whole type or individual members. + +### No `static` variables + +At the moment, there is no proposal to support +[`static` member variables](https://en.wikipedia.org/wiki/Class_variable#Static_member_variables_and_static_member_functions), +in line with avoiding global variables more generally. Carbon may need some +support in this area, though, for parity with and migration from C++. + +### Computed properties + +Carbon might want to support members of a type that are accessed like a data +member but return a computed value like a function. This has a number of +implications: + +- It would be a way of publicly exposing data members for + [encapsulated types](#encapsulated-types), allowing for rules that otherwise + forbid mixing public and private data members. +- It would provide a more graceful evolution path from a + [data class](#data-classes) to an [encapsulated type](#encapsulated-types). +- It would give an option to start with a [data class](#data-classes) instead + of writing all the boilerplate to create an + [encapsulated type](#encapsulated-types) preemptively to allow future + evolution. +- It would let you take a variable away and put a property in its place with + no other code changes. The number one use for this is so you can put a + breakpoint in the property code, then later go back to public variable once + you understand who was misbehaving. +- We should have some guidance for when to use a computed property instead of + a function with no arguments. One possible criteria is when it is a pure + function of the state of the object and executes in an amount of time + similar to ordinary member access. + +However, there are likely to be differences between computed properties and +other data members, such as the ability to take the address of them. We might +want to support "read only" data members, that can be read through the public +api but only modified with private access, for data members which may need to +evolve into a computed property. There are also questions regarding how to +support assigning or modifying computed properties, such as using `+=`. + +### Interfaces implemented for data classes + +We should define a way for defining implementations of interfaces for struct +types. To satisfy coherence, these implementations would have to be defined in +the library with the interface definition. The syntax might look like: + +``` +interface ConstructWidgetFrom { + fn Construct(Self) -> Widget; +} + +external impl {.kind: WidgetKind, .size: Int} + as ConstructWidgetFrom { ... } +``` + +In addition, we should define a way for interfaces to define templated blanket +implementations for [data classes](#data-classes) more generally. These +implementations will typically subject to the criteria that all the data fields +of the type must implement the interface. An example use case would be to say +that a data class is serializable if all of its fields were. For this we will +need a type-of-type for capturing that criteria, maybe something like +`DataFieldsImplement(MyInterface)`. The templated implementation will need some +way of iterating through the fields so it can perform operations fieldwise. This +feature should also implement the interfaces for any tuples whose fields satisfy +the criteria. + +It is an open question how define implementations for binary operators. For +example, if `Int` is comparable to `Float32`, then `{.x = 3, .y = 2.72}` should +be comparable to `{.x = 3.14, .y = 2}`. The trick is how to declare the criteria +that "`T` is comparable to `U` if they have the same field names in the same +order, and for every field `x`, the type of `T.x` implements `ComparableTo` for +the type of `U.x`." diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index fc5f3c3c3f21d..81af2e1afb064 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -378,7 +378,7 @@ There are a few obstacles to supporting dynamic dispatch efficiently, which may limit the extent it is used automatically by implementations. For example, the following features would benefit substantially from guaranteed monomorphization: -- Field packing in struct layout. For example, packing a `Bool` into the lower +- Field packing in class layout. For example, packing a `Bool` into the lower bits of a pointer, or packing bit-fields with generic widths. - Allocating local variables in stack storage. Without monomorphization, we would need to perform dynamic memory allocation -- whether on the stack or diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index dfb1375c62137..fbb1e3bc6f000 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -185,7 +185,7 @@ The `interface` keyword is used to define a need to explicitly implement them, using an `impl` block, such as here: ``` -struct Song { +class Song { // ... // Implementing `Printable` for `Song` inside the definition of `Song` @@ -206,13 +206,12 @@ external impl Song as Comparable { } ``` -Implementations may be defined within the struct definition itself or -externally. External implementations may be defined in the library defining the -interface. +Implementations may be defined within the class definition itself or externally. +External implementations may be defined in the library defining the interface. #### Qualified and unqualified access -The methods of an interface implemented within the struct definition may be +The methods of an interface implemented within the class definition may be called with the unqualified syntax. All methods of implemented interfaces may be called with the qualified syntax, whether they are defined internally or externally. @@ -356,7 +355,7 @@ A type may implement the parent interface implicitly by implementing all the methods in the child implementation. ``` -struct Key { +class Key { // ... impl as Hashable { fn IsEqual[me: Key](that: Key) -> Bool { ... } @@ -441,7 +440,7 @@ type-of-type. For example: If there were a class `CDCover` defined this way: ``` -struct CDCover { +class CDCover { impl as Printable { ... } diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index c4dc04fcdb9cb..9f7e560b49e97 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -555,11 +555,11 @@ interface Container { ... fn Insert[addr me: Self*](position: IteratorType, value: ElementType); } -struct ListIterator(ElementType:! Type) { +class ListIterator(ElementType:! Type) { ... impl Iterator; } -struct List(ElementType:! Type) { +class List(ElementType:! Type) { // Iterator type is determined by the container type. let IteratorType: Iterator = ListIterator(ElementType); fn Insert[addr me: Self*](position: IteratorType, value: ElementType) { diff --git a/docs/design/structs.md b/docs/design/structs.md deleted file mode 100644 index 3df3ab0866ec2..0000000000000 --- a/docs/design/structs.md +++ /dev/null @@ -1,113 +0,0 @@ -# Structs - - - - - -## Table of contents - -- [TODO](#todo) -- [Overview](#overview) -- [Open questions](#open-questions) - - [`self` type](#self-type) - - [Default access control level](#default-access-control-level) - - - -## TODO - -This is a skeletal design, added to support [the overview](README.md). It should -not be treated as accepted by the core team; rather, it is a placeholder until -we have more time to examine this detail. Please feel welcome to rewrite and -update as appropriate. - -## Overview - -Beyond simple tuples, Carbon of course allows defining named product types. This -is the primary mechanism for users to extend the Carbon type system and -fundamentally is deeply rooted in C++ and its history (C and Simula). We simply -call them `struct`s rather than other terms as it is both familiar to existing -programmers and accurately captures their essence: they are a mechanism for -structuring data: - -``` -struct Widget { - var Int x; - var Int y; - var Int z; - - var String payload; -} -``` - -Most of the core features of structures from C++ remain present in Carbon, but -often using different syntax: - -``` -struct AdvancedWidget { - // Do a thing! - fn DoSomething(AdvancedWidget self, Int x, Int y); - - // A nested type. - struct NestedType { - // ... - } - - private var Int x; - private var Int y; -} - -fn Foo(AdvancedWidget thing) { - thing.DoSomething(1, 2); -} -``` - -Here we provide a public object method and two private data members. The method -explicitly indicates how the object parameter is passed to it, and there is no -automatic scoping - you have to use `self` here. The `self` name is also a -keyword, though, that explains how to invoke this method on an object. This -member function accepts the object _by value_, which is easily expressed here -along with other constraints on the object parameter. Private members work the -same as in C++, providing a layer of easy validation of the most basic interface -constraints. - -The type itself is a compile-time constant value. All name access is done with -the `.` notation. Constant members (including member types and member functions -which do not need an implicit object parameter) can be accessed by way of that -constant: `AdvancedWidget.NestedType`. Other members and member functions -needing an object parameter (or "methods") must be accessed from an object of -the type. - -Some things in C++ are notably absent or orthogonally handled: - -- No need for `static` functions, they simply don't take an initial `self` - parameter. -- No `static` variables because there are no global variables. Instead, can - have scoped constants. - -## Open questions - -### `self` type - -Requiring the type of `self` makes method declarations quite verbose. Unclear -what is the best way to mitigate this, there are many options. One is to have a -special `Self` type. - -It may be interesting to consider separating the `self` syntax from the rest of -the parameter pattern as it doesn't seem necessary to inject all of the special -rules (covariance versus contravariance, special pointer handling) for `self` -into the general pattern matching system. - -### Default access control level - -The default access control level, and the options for access control, are pretty -large open questions. Swift and C++ (especially w/ modules) provide a lot of -options and a pretty wide space to explore here. If the default isn't right most -of the time, access control runs the risk of becoming a significant ceremony -burden that we may want to alleviate with grouped access regions instead of -per-entity specifiers. Grouped access regions have some other advantages in -terms of pulling the public interface into a specific area of the type. diff --git a/docs/design/templates.md b/docs/design/templates.md index 59a3d7f9bbc5d..753a5e5f71077 100644 --- a/docs/design/templates.md +++ b/docs/design/templates.md @@ -44,7 +44,7 @@ are subject to full instantiation -- other parameters will be type checked and bound early to the extent possible. For example: ``` -struct Stack(Type$$ T) { +class Stack(Type$$ T) { var Array(T) storage; fn Push(T value); diff --git a/proposals/README.md b/proposals/README.md index 616c294ac55dd..a76c392543d22 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -59,6 +59,7 @@ request: - [0538 - `return` with no argument](p0538.md) - [0540 - Remove `Void`](p0540.md) - [0555 - Operator precedence](p0555.md) +- [0561 - Basic classes: use cases, struct literals, struct types, and future work](p0561.md) - [0601 - Operator tokens](p0601.md) - [0618 - var ordering](p0618.md) - [0623 - Require braces](p0623.md) diff --git a/proposals/p0561.md b/proposals/p0561.md new file mode 100644 index 0000000000000..5ea6b935886d6 --- /dev/null +++ b/proposals/p0561.md @@ -0,0 +1,179 @@ +# Basic classes: use cases, struct literals, struct types, and future work + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/561) + + + +## Table of contents + +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) +- [Alternatives considered](#alternatives-considered) + - [Earlier proposal](#earlier-proposal) + - [Interfaces implemented for anonymous data classes](#interfaces-implemented-for-anonymous-data-classes) + - [Access control](#access-control) + - [Introducer for structural data class types](#introducer-for-structural-data-class-types) + - [Terminology](#terminology) + + + +## Problem + +We need to say how you define new types in Carbon. This proposal is specifically +about [record types](). +The proposal is not intended to be a complete story for record types, but enough +to get agreement on direction. It primarily focuses on: + +- use cases including: data classes, encapsulated types with virtual and + non-virtual methods and optional single inheritance, interfaces as base + classes that support multiple inheritance, and mixins for code reuse; +- anonymous structural data types for record literals used to initialize class + values and ad-hoc parameter and return types with named components; and +- future work, including the provisional syntax in use for features that have + not been decided. + +## Background + +This is a replacement for earlier proposal +[#98](https://github.com/carbon-language/carbon-lang/pull/98). + +## Proposal + +This proposal adds an initial design for record types called "classes", +including _structural data classes_ called _struct types_ as well as struct +literals. The design is replacing the skeletal design for what were called +"struct" types with a [new document on classes](/docs/design/classes.md). + +## 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 + +### Earlier proposal + +There was an earlier proposal +[#98](https://github.com/carbon-language/carbon-lang/pull/98), that made a +number of different choices, including: + +- Tuples were given named components instead of having separate struct + literals. +- No form of multiple inheritance was proposed. +- Operators were define using methods like C++ instead of by implementing + interfaces like Rust. +- Constructors had a special form like C++ instead of being regular functions + like Rust. +- Tuples and classes were both considered example of record types. +- Members of classes could be individually left uninitialized. +- Coverage of nominal types, inheritance, etc. were considered in much more + detail. + +### Interfaces implemented for anonymous data classes + +Whether we would support implementing interfaces for specific anonymous data +classes was +[discussed on Discord](https://discord.com/channels/655572317891461132/709488742942900284/867471671089561643). +[The conclusion](https://discord.com/channels/655572317891461132/709488742942900284/867516894029938710) +was "yes", reasoning that we would support that for the same reason as a number +of other cases such as tuple and pointer types. +[A specific use case](https://discord.com/channels/655572317891461132/709488742942900284/867517209026756630) +would be implementing interface + +``` +interface ConstructWidgetFrom { fn Construct(Self) -> Widget; } +``` + +for type `{.kind: WidgetKind, .size: Int}`. + +### Access control + +[Issue #665](https://github.com/carbon-language/carbon-lang/issues/665) decided +that by default members of a class would be publicly accessible. There were a +few reasons: + +- The readability of public members is the most important, since we expect + most readers to be concerned with the public API of a type. +- The members that are most commonly private are the data fields, which have + relatively less complicated definitions that suffer less from the extra + annotation. + +Additionally, there is precedent for this approach in modern object-oriented +languages such as +[Kotlin](https://kotlinlang.org/docs/visibility-modifiers.html) and +[Python](https://docs.python.org/3/tutorial/classes.html), both of which are +well regarded for their usability. + +It further decided that members would be given more restricted access using a +local annotation on the declaration itself rather than a block or region +approach such as used in C++. This is primarily motivated by a desire to reduce +context sensitivity, following +[the principle](/docs/project/principles/low_context_sensitivity.md) introduced +in [#646](https://github.com/carbon-language/carbon-lang/pull/646). It helps +readers to more easily determine the accessibility of a member in large classes, +say when they have jumped to a specific definition in their IDE. + +### Introducer for structural data class types + +[Issue #653](https://github.com/carbon-language/carbon-lang/issues/653) +discussed whether structural data class types should have an introducer to +distinguish them from structural data class literals. Ultimately we decided no +introducer was needed: + +- Outside of `{}`, types could be distinguished from literal values by the + presence of a `:` after the first field name. +- This creates a sort of consistency: introducers are frequently used when + introducing new names, as in `fn`, `var`, `interface`, and so on. Struct + type declarations don't introduce new names so they don't require an + introducer. +- It avoids having a different introducer for things that are still treated as + classes for many purposes. This means we won't have to frequently say + "struct or class" in documentation. +- We do want to use these type expressions in contexts that will benefit from + being more concise, such as inside function and variable declarations. + +This does cause an issue that `{}` is both an empty struct literal and empty +struct type. However, we've already accepted that complexity with tuples, so +this choice is more consistent. If we find that we need an introducer for tuple +types to distinguish the empty tuple from its type, we expect to find that we +have the same problem with empty struct literals, and the other way around. We +are explicitly to choosing to accept the risk that this won't work out in order +to have a more concise syntax in case it does. + +### Terminology + +Do literals have "class" type or are they some other kind of type? +[Issue #651](https://github.com/carbon-language/carbon-lang/issues/651) decided +that all of these types were different kinds of classes: + +- Literals like `{.a = 2}` are "structural data class literals" or "struct + literals" for short. Here "structural" means that two types are considered + equal if their fields match. They are not "nominal" since they don't have a + name to use for type equality. +- The types of those literals like `{.a: i64}` would be "structural data + classes" or "struct types" for short. +- There would also be "nominal data classes" that are declared with a syntax + more similar to other nominal classes. + +We preferred to refer to all of these as class types, rather than have to +frequently refer to "struct or class types", adding additional words to name +more specific subsets, like "data classes". In contrast, tuple types are not +considered classes, but classes and tuples together form _product types_. + +The term "class" was chosen over "struct" since they generally support +object-oriented features like encapsulation, inheritance, and dynamic dispatch, +and how C++ programmers generally refer to their record types. These were +considered more significant than the C++ distinction that classes default to +private access control. Since we plan to use a different syntax in Carbon to +specify access restrictions, the different default seemed straightforward to +teach.