mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Basic classes: use cases, struct literals, struct types, and future work (#561)
This proposal defines the very basics of `class` types, primarily focused on:
- use cases including: data classes, encapsulated types, inheritance with and without `virtual`, interfaces as base classes, and mixins for code reuse;
- anonymous data types for called _structural data classes_ or _struct types_. Struct literals are used to initialize class values and ad-hoc parameter and return types with named components; and
- future work, including the provisional syntax already in use for features that have not been decided.
The intent is to both make some small incremental progress and get agreement on direction. As such it doesn't include things like nominal types, methods, access control, inheritance, etc.
It proposes this struct type and literal syntax:
```
var p: {.x: Int, .y: Int} = {.x = 0, .y = 1};
```
Note that it uses commas (`,`) between fields instead of semicolons (`;`), and no introducer for types or literal values.
Incorporates decisions from #665 , #653 , #651
Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Geoff Romer
Chandler Carruth
parent
18e3969ded
commit
36764ff1af
+24
-50
@@ -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:
|
||||
|
||||
```carbon
|
||||
struct AdvancedWidget {
|
||||
// Do a thing!
|
||||
fn DoSomething(self: AdvancedWidget, x: Int, y: Int);
|
||||
|
||||
// A nested type.
|
||||
struct Nestedtype {
|
||||
// ...
|
||||
}
|
||||
|
||||
private var x: Int;
|
||||
private var y: Int;
|
||||
}
|
||||
|
||||
fn Foo(thing: AdvancedWidget) {
|
||||
thing.DoSomething(1, 2);
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
> **TODO:** Needs a feature design and a high level summary provided inline.
|
||||
You may use a _structural data class literal_, also known as a _struct literal_,
|
||||
to assign or initialize a variable with a class type.
|
||||
|
||||
```carbon
|
||||
var sprocket: Widget = {.x = 3, .y = 4, .z = 5, .payload = "Sproing"};
|
||||
sprocket = {.x = 2, .y = 1, .z = 0, .payload = "Bounce"};
|
||||
```
|
||||
|
||||
You may also copy one struct into another of the same type.
|
||||
|
||||
```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);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
...
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# Structs
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [TODO](#todo)
|
||||
- [Overview](#overview)
|
||||
- [Open questions](#open-questions)
|
||||
- [`self` type](#self-type)
|
||||
- [Default access control level](#default-access-control-level)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## 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.
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# Basic classes: use cases, struct literals, struct types, and future work
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
[Pull request](https://github.com/carbon-language/carbon-lang/pull/561)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## 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)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Problem
|
||||
|
||||
We need to say how you define new types in Carbon. This proposal is specifically
|
||||
about [record types](<https://en.wikipedia.org/wiki/Record_(computer_science)>).
|
||||
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.
|
||||
Reference in New Issue
Block a user