mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:50:14 +01:00
This should handle over-long lines. I had tried to make the normalize method work, but it doesn't seem promising and so let's at least enable this version. Assisted-by: Antigravity with Gemini
1549 lines
75 KiB
Markdown
1549 lines
75 KiB
Markdown
# Values, variables, and pointers
|
|
|
|
<!--
|
|
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
|
|
|
|
- [Values, objects, and expressions](#values-objects-and-expressions)
|
|
- [Expression categories](#expression-categories)
|
|
- [Value acquisition](#value-acquisition)
|
|
- [Direct initialization](#direct-initialization)
|
|
- [Copy initialization](#copy-initialization)
|
|
- [Temporary materialization](#temporary-materialization)
|
|
- [Binding patterns and local variables with `let` and `var`](#binding-patterns-and-local-variables-with-let-and-var)
|
|
- [Local variables](#local-variables)
|
|
- [Consuming function parameters](#consuming-function-parameters)
|
|
- [Reference expressions](#reference-expressions)
|
|
- [Entire reference expressions](#entire-reference-expressions)
|
|
- [Durable reference expressions](#durable-reference-expressions)
|
|
- [Ephemeral reference expressions](#ephemeral-reference-expressions)
|
|
- [Value expressions](#value-expressions)
|
|
- [Comparison to C++ parameters](#comparison-to-c-parameters)
|
|
- [Polymorphic types](#polymorphic-types)
|
|
- [Interop with C++ `const &` and `const` methods](#interop-with-c-const--and-const-methods)
|
|
- [Escape hatches for value addresses in Carbon](#escape-hatches-for-value-addresses-in-carbon)
|
|
- [Initializing expressions](#initializing-expressions)
|
|
- [Function calls and returns](#function-calls-and-returns)
|
|
- [Deferred initialization from values and references](#deferred-initialization-from-values-and-references)
|
|
- [Declared `returned` variable](#declared-returned-variable)
|
|
- [Extended types](#extended-types)
|
|
- [Initializing results](#initializing-results)
|
|
- [Extended type conversions](#extended-type-conversions)
|
|
- [Type conversions](#type-conversions)
|
|
- [Category conversions](#category-conversions)
|
|
- [Pointers](#pointers)
|
|
- [Reference types](#reference-types)
|
|
- [Pointer syntax](#pointer-syntax)
|
|
- [Dereferencing customization](#dereferencing-customization)
|
|
- [`const`-qualified types](#const-qualified-types)
|
|
- [Lifetime overloading](#lifetime-overloading)
|
|
- [Value representation and customization](#value-representation-and-customization)
|
|
- [Alternatives considered](#alternatives-considered)
|
|
- [References](#references)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Values, objects, and expressions
|
|
|
|
Carbon has both abstract _values_ and concrete _objects_. Carbon _values_ are
|
|
things like `42`, `true`, and `i32` (a type value). Carbon _objects_ have
|
|
_storage_ where values can be read and written. Storage also allows taking the
|
|
address of an object in memory in Carbon.
|
|
|
|
Both objects and values can be nested within each other. For example
|
|
`(true, true)` is both a value and also contains two sub-values. When a
|
|
two-tuple is stored somewhere, it is both a tuple-typed object and contains two
|
|
subobjects.
|
|
|
|
These terms are important components in the describing the semantics of Carbon
|
|
code, but they aren't sufficient. We also need to explicitly and precisely talk
|
|
about the Carbon _expressions_ that produce or reference values and objects.
|
|
Categorizing the expressions themselves allows us to be more precise and
|
|
articulate important differences not captured without looking at the expression
|
|
itself.
|
|
|
|
### Expression categories
|
|
|
|
There are three primary expression categories in Carbon:
|
|
|
|
- [_Value expressions_](#value-expressions) produce abstract, read-only
|
|
_values_ that cannot be modified or have their address taken.
|
|
- [_Reference expressions_](#reference-expressions) refer to _objects_ with
|
|
_storage_ where a value may be read or written and the object's address can
|
|
be taken.
|
|
- [_Initializing expressions_](#initializing-expressions) require a result
|
|
location to be provided implicitly when evaluating the expression. The
|
|
expression then initializes an object in that location. These are used to
|
|
model function returns, which can construct the returned value directly in
|
|
the caller's storage.
|
|
|
|
Expressions in one category can be implicitly converted to any other primary
|
|
category when needed. The primitive conversion steps used are:
|
|
|
|
- [_Value acquisition_](#value-acquisition) forms a value expression from the
|
|
current value of the object referenced by a reference expression.
|
|
- [_Direct initialization_](#direct-initialization) converts a value
|
|
expression into an initializing expression.
|
|
- [_Copy initialization_](#copy-initialization) converts a reference
|
|
expression into an initializing expression.
|
|
- [_Temporary materialization_](#temporary-materialization) converts an
|
|
initializing expression into a reference expression.
|
|
|
|
These conversion steps combine to provide the transitive conversion table:
|
|
|
|
| From: | value | reference | initializing |
|
|
| ------------------: | ------------------------- | --------- | --------------------- |
|
|
| to **value** | == | acquire | materialize + acquire |
|
|
| to **reference** | direct init + materialize | == | materialize |
|
|
| to **initializing** | direct init | copy init | == |
|
|
|
|
Reference expressions are divided into 2x2 sub-categories: they can be either
|
|
[_ephemeral_](#ephemeral-reference-expressions) or
|
|
[_durable_](#durable-reference-expressions), and either _entire_ or
|
|
_non-entire_.
|
|
|
|
Ephemeral reference expressions are formed through temporary materialization,
|
|
and have restrictions on how they are used. In contrast, durable reference
|
|
expressions refer to storage that outlives the expression, and typically has a
|
|
declared name. Entire reference expressions can only refer to complete objects,
|
|
whereas non-entire reference expressions can refer to both complete objects and
|
|
sub-objects (such as class fields and base class sub-objects). As a consequence,
|
|
only entire reference expressions can be destructively moved.
|
|
|
|
> **Future work:** This means that pointer-dereference expressions are
|
|
> non-entire, but we will presumably want to be able to destructively move from
|
|
> them. We need to figure out how to support that without violating the
|
|
> invariant that a live object has live fields.
|
|
|
|
Value acquisition and copy initialization can be applied to any reference
|
|
expression, but materialization only produces ephemeral entire reference
|
|
expressions. An entire reference expression can be implicitly converted to
|
|
non-entire; this has no run-time effect because it merely discards static
|
|
object-completeness information. Non-entire reference expressions can only be
|
|
converted to entire reference expressions by round-tripping through
|
|
copy-initialization and materialization. Non-durable-reference expressions
|
|
cannot be implicitly converted to durable reference expressions at all.
|
|
|
|
> **TODO:** Determine how these reference sub-categories relate to memory-safety
|
|
> properties like uniqueness, and make sure their names are aligned with
|
|
> memory-safety terminology.
|
|
|
|
#### Value acquisition
|
|
|
|
We call forming a value expression from a reference expression _value
|
|
acquisition_. This forms a value expression that will evaluate to the value of
|
|
the object in the referenced storage of the reference expression. It may do this
|
|
by eagerly reading that value into a machine register, lazily reading that value
|
|
on-demand into a machine register, or in some other way modeling that abstract
|
|
value.
|
|
|
|
See the [value expressions](#value-expressions) section for more details on the
|
|
semantics of value expressions.
|
|
|
|
#### Direct initialization
|
|
|
|
This is the first way we have of initializing storage of an object. There may
|
|
not be storage for the source of this initialization, as the value expression
|
|
used for the initialization may be in a machine register or simply be abstractly
|
|
modeled like a source literal. A canonical example here is zeroing an object.
|
|
|
|
#### Copy initialization
|
|
|
|
This initializes storage for an object based on some other object which already
|
|
has initialized storage. A classic example here are types which can be copied
|
|
trivially and where this is implemented as a `memcpy` of their underlying bytes.
|
|
|
|
#### Temporary materialization
|
|
|
|
We use temporary materialization when we need to initialize an object by way of
|
|
storage, but weren't provided dedicated storage and can simply acquire the
|
|
result as a value afterward.
|
|
|
|
> **Open question:** The lifetimes of temporaries is not yet specified.
|
|
|
|
## Binding patterns and local variables with `let` and `var`
|
|
|
|
A [_value binding pattern_](/docs/design/README.md#binding-patterns) introduces
|
|
a name that is a [_value expression_](#value-expressions) and is called a _value
|
|
binding_. This is the desired default for many pattern contexts, especially
|
|
function parameters. Values are a good model for "input" function parameters
|
|
which are the dominant and default style of function parameters:
|
|
|
|
```carbon
|
|
fn Sum(x: i32, y: i32) -> i32 {
|
|
// `x` and `y` are value expressions here. We can use their value, but not
|
|
// modify them or take their address.
|
|
return x + y;
|
|
}
|
|
```
|
|
|
|
Value bindings require the matched expression to be a _value expression_,
|
|
converting it into one as necessary.
|
|
|
|
A _variable pattern_ is introduced with the `var` keyword. The matched
|
|
expression must be an ephemeral entire reference expression (which typically
|
|
requires the matched expression to be materialized); the `var` pattern takes
|
|
ownership of the newly-allocated temporary storage it refers to, which extends
|
|
its lifetime to the end of the enclosing scope. The subpattern is then matched
|
|
against a _durable_ entire reference expression to the object in that storage.
|
|
|
|
> **Open question:** This implies that `var field: T = F().field;` doesn't
|
|
> perform any copies or moves on `T`. This, in turn, implies that the storage
|
|
> for `field` must be laid out as part of a complete `typeof(F())` object
|
|
> layout, which is initialized by the call to `F()`. All other members of that
|
|
> layout are immediately destroyed, and their storage is theoretically reusable
|
|
> after that point, but it's unclear if this is the right default, or how to
|
|
> enable user code to override that default when it's the wrong tradeoff.
|
|
|
|
A _reference binding pattern_ is a binding pattern that is nested under a `var`
|
|
pattern. It introduces a name called a _reference binding_ that is a
|
|
[durable reference expression](#durable-reference-expressions) to an object
|
|
within the variable pattern's storage.
|
|
|
|
```carbon
|
|
fn MutateThing(ptr: i64*);
|
|
|
|
fn Example() {
|
|
// `1` starts as a value expression, which is what a value binding expects.
|
|
let x: i64 = 1;
|
|
|
|
// `2` also starts as a value expression, but the variable pattern requires it
|
|
// to be converted to an ephemeral entire reference expression by using the
|
|
// value `2` to initialize temporary storage, which the variable pattern
|
|
// takes ownership of. The reference binding pattern is then bound to a
|
|
// durable reference to the newly-initialized object.
|
|
var y: i64 = 2;
|
|
|
|
// Allowed to take the address and mutate `y` as it is a durable reference
|
|
// expression.
|
|
MutateThing(&y);
|
|
|
|
// ❌ This would be an error though due to trying to take the address of the
|
|
// value expression `x`.
|
|
MutateThing(&x);
|
|
}
|
|
```
|
|
|
|
### Local variables
|
|
|
|
A local binding pattern can be introduced with either the `let` or `var`
|
|
keyword. The `let` introducer begins a value pattern which works the same as the
|
|
default patterns in other contexts. The `var` introducer immediately begins a
|
|
variable pattern.
|
|
|
|
- `let` _identifier_`:` _( expression |_ `auto` _)_ `=` _value_`;`
|
|
- `var` _identifier_`:` _( expression |_ `auto` _) [_ `=` _value ]_`;`
|
|
|
|
These are just simple examples of binding patterns used directly in local
|
|
declarations. Local `let` and `var` declarations build on Carbon's general
|
|
[pattern matching](/docs/design/pattern_matching.md) design. `var` declarations
|
|
implicitly start off within a `var` pattern. `let` declarations introduce
|
|
patterns that bind values by default, the same as function parameters and most
|
|
other pattern contexts.
|
|
|
|
The general pattern matching model also allows nesting `var` sub-patterns within
|
|
a larger pattern that defaults to matching values. For example, we can combine
|
|
the two local declarations above into one destructuring declaration with an
|
|
inner `var` pattern here:
|
|
|
|
```carbon
|
|
fn DestructuringExample() {
|
|
// Both `1` and `2` start as value expressions. The `x` binding directly
|
|
// matches `1`. For `2`, the variable pattern requires it to be converted to
|
|
// an ephemeral entire reference expression by using the value `2` to
|
|
// initialize temporary storage, which the variable pattern takes ownership
|
|
// of. The reference binding `y` is then bound to a durable reference to the
|
|
// newly-initialized object.
|
|
let (x: i64, var y: i64) = (1, 2);
|
|
|
|
// Just like above, we can take the address and mutate `y`:
|
|
MutateThing(&y);
|
|
|
|
// ❌ And this remains an error:
|
|
MutateThing(&x);
|
|
}
|
|
```
|
|
|
|
If `auto` is used in place of the type for a local binding pattern,
|
|
[type inference](type_inference.md) is used to automatically determine the
|
|
variable's type.
|
|
|
|
These local bindings introduce names scoped to the code block in which they
|
|
occur, which will typically be marked by an open brace (`{`) and close brace
|
|
(`}`).
|
|
|
|
### Consuming function parameters
|
|
|
|
Just as part of a `let` declaration can use a `var` prefix to become a variable
|
|
pattern and bind names that will form reference expressions to the variable's
|
|
storage, so can function parameters:
|
|
|
|
```carbon
|
|
fn Consume(var x: SomeData) {
|
|
// We can mutate and use variable that `x` refers to here.
|
|
}
|
|
```
|
|
|
|
This allows us to model an important special case of function inputs -- those
|
|
that are _consumed_ by the function, either through local processing or being
|
|
moved into some persistent storage. Marking these in the pattern and thus
|
|
signature of the function changes the expression category required for arguments
|
|
in the caller. These arguments are required to be _ephemeral entire reference
|
|
expressions_, potentially being converted into such an expression if necessary,
|
|
whose storage will be dedicated-to and owned-by the function parameter.
|
|
|
|
This pattern serves the same purpose as C++'s pass-by-value when used with types
|
|
that have non-trivial resources attached to pass ownership into the function and
|
|
consume the resource. But rather than that being the seeming _default_, Carbon
|
|
makes this a use case that requires a special marking on the declaration.
|
|
|
|
## Reference expressions
|
|
|
|
_Reference expressions_ refer to _objects_ with _storage_ where a value may be
|
|
read or written and the object's address can be taken.
|
|
|
|
Reference expressions can be either _durable_ or _ephemeral_. These refine the
|
|
_lifetime_ of the underlying storage and provide safety restrictions reflecting
|
|
that lifetime. Reference expressions can also be either _entire_ or
|
|
_non-entire_, depending on whether the referenced object is known to be complete
|
|
(rather than a sub-object of another object).
|
|
|
|
### Entire reference expressions
|
|
|
|
An _entire reference expression_ is one that is statically known to refer to a
|
|
complete object. Other references are _non-entire_. Durable and ephemeral
|
|
reference expressions can both be either entire or non-entire (although
|
|
non-entire ephemeral references are rare). Unless otherwise specified, an
|
|
expression or operation that produces a reference produces a non-entire
|
|
reference.
|
|
|
|
Note that a non-entire reference expression still _might_ refer to a complete
|
|
object; the language rules just don't _guarantee_ that is does. As a result, an
|
|
entire reference can be implicitly converted to a non-entire reference (with the
|
|
same durability), because this merely discards the knowledge that the object is
|
|
complete. By the same token, there is no context that requires a non-entire
|
|
reference; only contexts that accept both, that accept only entire references,
|
|
or that don't accept references at all.
|
|
|
|
Currently, the only context that requires an entire reference is the scrutinee
|
|
of a `var` pattern, which is required to be an entire ephemeral reference (and
|
|
is [converted](#category-conversions) to that category if necessary).
|
|
|
|
> **Note:** This extends the lifetime of the reference, so it must be possible
|
|
> to determine _which_ temporary an ephemeral entire reference refers to, so
|
|
> that the implementation knows which lifetime to extend. Under the current
|
|
> language rules, this can be done statically.
|
|
|
|
> **Open question:** Should we extend the language in ways that would force that
|
|
> determination to be dynamic? For example, should we allow
|
|
> `if c then r1 else r2` to be an entire ephemeral reference expression if `r1`
|
|
> and `r2` are? As a more extreme example, should we support functions that take
|
|
> and return entire ephemeral references?
|
|
|
|
There are several kinds of expressions that produce entire references. For
|
|
example:
|
|
|
|
- The name of an object introduced with a
|
|
[variable binding pattern](pattern_matching.md#name-binding-patterns) (in
|
|
other words, a name that was declared with `var <name> : <type>`) is a
|
|
durable entire reference.
|
|
- a member access expression `x.member` or `x.(member)` is an entire reference
|
|
if `x` is an initializing or entire ephemeral reference expression with a
|
|
struct or tuple type.
|
|
- The result of materialization is an entire ephemeral reference.
|
|
- When a [tuple pattern](pattern_matching.md#tuple-patterns) or
|
|
[struct pattern](pattern_matching.md#struct-patterns) is matched with an
|
|
ephemeral entire reference scrutinee, that scrutinee is destructured into
|
|
ephemeral entire references to its elements, which are then matched with the
|
|
corresponding subpatterns.
|
|
|
|
### Durable reference expressions
|
|
|
|
_Durable reference expressions_ are those where the object's storage outlives
|
|
the full expression and the address could be meaningfully propagated out of it
|
|
as well.
|
|
|
|
There are several contexts where durable reference expressions are required. For
|
|
example:
|
|
|
|
- [Assignment statements](/docs/design/assignment.md) require the
|
|
left-hand-side of the `=` to be a durable reference. This stronger
|
|
requirement is enforced before the expression is rewritten to dispatch into
|
|
the `Carbon.Assign.Op` interface method.
|
|
- [Address-of expressions](#pointer-syntax) require their operand to be a
|
|
durable reference and compute the address of the referenced object.
|
|
- [`ref` binding patterns](pattern_matching.md#name-binding-patterns) require
|
|
their scrutinee to be a durable reference.
|
|
- If a function's [extended return type](#function-calls-and-returns) contains
|
|
`ref` tags, `return` statements require the corresponding parts of the
|
|
operand to be durable reference expressions.
|
|
|
|
There are also several kinds of expressions that produce durable references. For
|
|
example:
|
|
|
|
- Names of objects introduced with a
|
|
[reference binding](#binding-patterns-and-local-variables-with-let-and-var):
|
|
`x`
|
|
- Dereferenced [pointers](#pointers): `*p`
|
|
- Names of subobjects through member access to some other durable reference
|
|
expression: `x.member` or `p->member`
|
|
- [Indexing](/docs/design/expressions/indexing.md) into a type similar to
|
|
C++'s `std::span` that implements `IndirectIndexWith`, or indexing into any
|
|
type with a durable reference expression such as `local_array[i]`.
|
|
- Calls to functions whose
|
|
[extended return types](#function-calls-and-returns) contain `ref`.
|
|
|
|
Durable reference expressions can only be produced _directly_ by one of these
|
|
expressions. They are never produced by converting one of the other expression
|
|
categories into a reference expression.
|
|
|
|
### Ephemeral reference expressions
|
|
|
|
We call the reference expressions formed through
|
|
[temporary materialization](#temporary-materialization) _ephemeral reference
|
|
expressions_. They still refer to an object with storage, but it may be storage
|
|
that will not outlive the full expression, and so it can't be used where a
|
|
durable reference is expected.
|
|
|
|
> **Future work:** The current design does not support mutating ephemeral
|
|
> references (or initializing expressions): assigning to an ephemeral reference
|
|
> is disallowed directly, and invoking mutating methods is disallowed because
|
|
> the `ref self` parameter can only bind to a durable reference. In C++ it's
|
|
> unusual but not rare to intentionally mutate a temporary, such as in a
|
|
> builder-style method chain (for example `MakeFoo().SetBar().AddBaz()`), so
|
|
> Carbon will need to provide some interop and migration target for that kind of
|
|
> code.
|
|
|
|
There is one context that requires an ephemeral reference expression in Carbon:
|
|
the scrutinee of a
|
|
[`var` pattern](#binding-patterns-and-local-variables-with-let-and-var) (which
|
|
also requires the reference to be entire).
|
|
|
|
There are only a few ways to produce an ephemeral reference expression. Most
|
|
notably:
|
|
|
|
- The result of materialization is an entire ephemeral reference.
|
|
- A member access expression `x.member` or `x.(member)` is an ephemeral
|
|
reference if `x` is an initializing or ephemeral reference.
|
|
- When a [tuple pattern](pattern_matching.md#tuple-patterns) or
|
|
[struct pattern](pattern_matching.md#struct-patterns) is matched with an
|
|
initializing or ephemeral reference scrutinee, that scrutinee is
|
|
destructured into ephemeral references to its elements, which are then
|
|
matched with the corresponding subpatterns.
|
|
|
|
## Value expressions
|
|
|
|
A value cannot be mutated, cannot have its address taken, and may not have
|
|
storage at all or a stable address of storage. Values are abstract constructs
|
|
like function input parameters and constants. They can be formed in two ways --
|
|
a literal expression like `42`, or by reading the value of some stored object.
|
|
|
|
A core goal of values in Carbon is to provide a single model that can get both
|
|
the efficiency of passing by value when working with small types such as those
|
|
that fit into a machine register, but also the efficiency of minimal copies when
|
|
working with types where a copy would require extra allocations or other costly
|
|
resources. This directly helps programmers by providing a simpler model to
|
|
select the mechanism of passing function inputs. But it is also important to
|
|
enable generic code that needs a single type model that will have consistently
|
|
good performance.
|
|
|
|
When forming a value expression from a reference expression, Carbon
|
|
[acquires](#value-acquisition) the value of the referenced object. This allows
|
|
immediately reading from the object's storage into a machine register or a copy
|
|
if desired, but does not require that. The read of the underlying object can
|
|
also be deferred until the value expression itself is used. Once an object is
|
|
bound to a value expression in this way, any mutation to the object or its
|
|
storage ends the lifetime of the value binding, and makes any use of the value
|
|
expression an error.
|
|
|
|
> Note: this is _not_ intended to ever become "undefined behavior", but instead
|
|
> just "erroneous". We want to be able to detect and report such code as having
|
|
> a bug, but do not want unbounded UB and are not aware of important
|
|
> optimizations that this would inhibit.
|
|
>
|
|
> _Open issue:_ We need a common definition of erroneous behavior that we can
|
|
> use here (and elsewhere). Once we have that, we should cite it here.
|
|
|
|
> Note: this restriction is also **experimental** -- we may want to strengthen
|
|
> or weaken it based on experience, especially with C++ interop and a more
|
|
> complete memory safety story.
|
|
|
|
Even with these restrictions, we expect to make values in Carbon useful in
|
|
roughly the same places as `const &`s in C++, but with added efficiency in the
|
|
case where the values can usefully be kept in machine registers. We also
|
|
specifically encourage a mental model of a `const &` with extra efficiency.
|
|
|
|
The actual _representation_ of a value when bound, especially across function
|
|
boundaries, is [customizable](#value-representation-and-customization) by the
|
|
type. The defaults are based around preserving the baseline efficiency of C++'s
|
|
`const &`, but potentially reading the value when that would be both correct and
|
|
reliably more efficient, such as into a machine register.
|
|
|
|
### Comparison to C++ parameters
|
|
|
|
While these are called "values" in Carbon, they are not related to "by-value"
|
|
parameters as they exist in C++. The semantics of C++'s by-value parameters are
|
|
defined to create a new local copy of the argument, although it may move into
|
|
this copy.
|
|
|
|
Carbon's values are much closer to a `const &` in C++ with extra restrictions
|
|
such as allowing copies under "as-if" and preventing taking the address.
|
|
Combined, these restrictions allow implementation strategies such as in-register
|
|
parameters.
|
|
|
|
### Polymorphic types
|
|
|
|
Value expressions and value bindings can be used with
|
|
[polymorphic types](/docs/design/classes.md#inheritance), for example:
|
|
|
|
```carbon
|
|
base class MyBase { ... }
|
|
|
|
fn UseBase(b: MyBase) { ... }
|
|
|
|
class Derived {
|
|
extend base: MyBase;
|
|
...
|
|
}
|
|
|
|
fn PassDerived() {
|
|
var d: Derived = ...;
|
|
// Allowed to pass `d` here:
|
|
UseBase(d);
|
|
}
|
|
```
|
|
|
|
This is still allowed to create a copy or to move, but it must not _slice_. Even
|
|
if a copy is created, it must be a `Derived` object, even though this may limit
|
|
the available implementation strategies.
|
|
|
|
> **Future work:** The interaction between a
|
|
> [custom value representation](#value-representation-and-customization) and a
|
|
> value expression used with a polymorphic type needs to be fully captured.
|
|
> Either it needs to restrict to a `const ref` style representation (to prevent
|
|
> slicing) or it needs to have a model for the semantics when a different value
|
|
> representation is used.
|
|
|
|
### Interop with C++ `const &` and `const` methods
|
|
|
|
While value expressions cannot have their address taken in Carbon, they should
|
|
be interoperable with C++ `const &`s and C++ `const`-qualified methods. This
|
|
will in-effect "pin" some object (potentially a copy or temporary) into memory
|
|
and allow C++ to take its address. Without supporting this, values would likely
|
|
create an untenable interop ergonomic barrier. However, this does create some
|
|
additional constraints on value expressions and a way that their addresses can
|
|
escape unexpectedly.
|
|
|
|
Despite interop requiring an address to implement, C++ allows `const &`
|
|
parameters to bind to temporary objects where that address doesn't have much
|
|
meaning and might not be valid once the called function returns. As a
|
|
consequence, we don't expect C++ interfaces using a `const &` to misbehave in
|
|
practice.
|
|
|
|
> **Future work:** when a type customizes its
|
|
> [value representation](#value-representation-and-customization), as currently
|
|
> specified this will break the use of `const &` C++ APIs with such a value. We
|
|
> should extend the rules around value representation customization to require
|
|
> that either the representation type can be converted to (a copy) of the
|
|
> customized type, or implements an interop-specific interface to compute a
|
|
> `const` pointer to the original object used to form the representation object.
|
|
> This will allow custom representations to either create copies for interop or
|
|
> retain a pointer to the original object and expose that for interop as
|
|
> desired.
|
|
|
|
Another risk is exposing Carbon's value expressions to `const &` parameters in
|
|
this way, as C++ allows casting away `const`. However, in the absence of
|
|
`mutable` members, casting away `const` does not make it safe to _mutate_
|
|
through a `const &` parameter (or a `const`-qualified method). C++ allows
|
|
`const &` parameters and `const` member functions to access objects that are
|
|
_declared_ `const`. These objects cannot be mutated, even if `const` is removed,
|
|
exactly the same as Carbon value expressions. In fact, these kinds of mutations
|
|
[break in real implementations](https://cpp.compiler-explorer.com/z/KMhTondaK).
|
|
The result is that Carbon's value expressions will work similarly to
|
|
`const`-declared objects in C++, and will interop with C++ code similarly well.
|
|
|
|
### Escape hatches for value addresses in Carbon
|
|
|
|
**Open question:** It may be necessary to provide some amount of escape hatch
|
|
for taking the address of values. The
|
|
[C++ interop](#interop-with-c-const--and-const-methods) above already takes
|
|
their address functionally. Currently, this is the extent of an escape hatch to
|
|
the restrictions on values.
|
|
|
|
If a further escape hatch is needed, this kind of fundamental weakening of the
|
|
semantic model would be a good case for some syntactic marker like Rust's
|
|
`unsafe`, although rather than a region, it would seem better to tie it directly
|
|
to the operation in question. For example:
|
|
|
|
```carbon
|
|
class S {
|
|
fn ValueMemberFunction(self);
|
|
fn RefMemberFunction(ref self: const Self);
|
|
}
|
|
|
|
fn F(s_value: S) {
|
|
// This is fine.
|
|
s_value.ValueMemberFunction();
|
|
|
|
// This requires an unsafe marker in the syntax.
|
|
s_value.unsafe RefMemberFunction();
|
|
}
|
|
```
|
|
|
|
The specific tradeoff here is covered in a proposal
|
|
[alternative](/proposals/p002006-values-variables-pointers-and-references.md#value-expression-escape-hatches).
|
|
|
|
## Initializing expressions
|
|
|
|
Storage in Carbon is initialized using _initializing expressions_. Their
|
|
evaluation takes a _result location_ as an implicit input, and produces an
|
|
initialized object at that location, although that object may still be
|
|
_unformed_.
|
|
|
|
**Future work:** More details on initialization and unformed objects should be
|
|
added to the design from the proposal
|
|
[#257](https://github.com/carbon-language/carbon-lang/pull/257), see
|
|
[#1993](https://github.com/carbon-language/carbon-lang/issues/1993). When added,
|
|
it should be linked from here for the details on the initialization semantics
|
|
specifically.
|
|
|
|
The simplest form of initializing expressions are value or durable reference
|
|
expressions that are converted into an initializing expression. Value
|
|
expressions are written directly into the storage to form a new object.
|
|
Reference expressions have the object they refer to copied into a new object in
|
|
the provided storage.
|
|
|
|
**Future work:** The design should be expanded to fully cover how copying is
|
|
managed and linked to from here.
|
|
|
|
There are no syntactic contexts in Carbon that always require an initializing
|
|
expression, and no expression syntax that always produces an initializing
|
|
expression. By default, function call expressions are initializing expressions,
|
|
and correspondingly the operand of `return` is required to be an initializing
|
|
expression, but this default can be overridden by the
|
|
[function signature](#function-calls-and-returns).
|
|
|
|
Initializing expressions can also be created implicitly, when attempting to
|
|
convert an expression into an ephemeral entire reference expression
|
|
(particularly to match a `var` pattern): the expression is first converted to an
|
|
initializing expression if necessary, and then temporary storage is materialized
|
|
to act as its output, and as the referent of the resulting ephemeral reference
|
|
expression.
|
|
|
|
### Function calls and returns
|
|
|
|
The [result](#extended-types) of a function call can have an almost arbitrary
|
|
extended type. The return clause of a function signature consists of `->`
|
|
followed by an _extended return type_, an expression-like syntax that specifies
|
|
not only the type component but also the extended type of the function call's
|
|
result. `return` expressions in the function body are expected to have that
|
|
extended type, and are converted to it if necessary. When a function is declared
|
|
without a return clause, it behaves from the caller's point of view as if the
|
|
return clause were `-> ()`, but `return` statements in the function body don't
|
|
take operands (and can be omitted at the end of the function).
|
|
|
|
In the common case, the extended return type is a type expression, in which case
|
|
calls are modeled directly as initializing expressions -- they require storage
|
|
as an input and when evaluated cause that storage to be initialized with an
|
|
object. This means that when a function call is used to initialize some variable
|
|
pattern as here:
|
|
|
|
```carbon
|
|
fn CreateMyObject() -> MyType {
|
|
return <return-expression>;
|
|
}
|
|
|
|
var x: MyType = CreateMyObject();
|
|
```
|
|
|
|
The `<return-expression>` in the `return` statement actually initializes the
|
|
storage provided for `x`. There is no "copy" or other step.
|
|
|
|
In the body of such a function, all `return` statement expressions are required
|
|
to be initializing expressions and in fact initialize the storage provided to
|
|
the function's call expression. This in turn causes the property to hold
|
|
_transitively_ across an arbitrary number of function calls and returns. The
|
|
storage is forwarded at each stage and initialized exactly once.
|
|
|
|
More generally, the syntax and semantics of an extended return type are as
|
|
follows:
|
|
|
|
- _return-clause_ ::= `->` _extended-return-type_
|
|
- _extended-return-type_ ::= _nesting-extended-return-type_ |
|
|
_auto-extended-return-type_
|
|
- _nesting-extended-return-type_ ::= _expression-extended-return-type_ |
|
|
_proper-extended-return-type_
|
|
|
|
Extended return types can usually be nested, but syntaxes involving `auto` can
|
|
only occur at top level. We further divide nesting extended return types into
|
|
expressions and "proper" extended return types, but this is just a technical
|
|
means of avoiding formal ambiguity in the grammar; it has no greater
|
|
significance.
|
|
|
|
- _category-tag_ ::= `val` | `ref` | `var`
|
|
|
|
These tags are used to specify "value", "non-entire durable reference", or
|
|
"initializing" expression category (respectively). Note that there is no way to
|
|
express an entire or ephemeral reference category in an extended return type.
|
|
|
|
- _auto-extended-return-type_ ::= _category-tag_? `auto`
|
|
|
|
This denotes a primitive extended type with runtime phase and a deduced type
|
|
component. The category is determined by _category-tag_ if present, or
|
|
"initializing" otherwise.
|
|
|
|
- _proper-extended-return-type_ ::= _category-tag_ _expression_
|
|
|
|
This denotes a primitive extended type with runtime phase, category
|
|
_category-tag_, and type "_expression_ `as type`".
|
|
|
|
- _expression-extended-return-type_ ::= _expression_
|
|
|
|
An expression with no _category-tag_ is equivalent to "`var` _expression_".
|
|
|
|
- _proper-extended-return-type_ ::= `(` [_expression-extended-return-type_
|
|
`,`]\* _proper-extended-return-type_ [`,` _nesting-extended-return-type_]\*
|
|
`,`? `)`
|
|
|
|
A tuple literal of extended return types denotes a tuple extended type whose
|
|
sub-extended-types are specified by the comma-separated elements. To avoid
|
|
formal ambiguity, this grammar rule requires at least one of the
|
|
sub-extended-types to be proper.
|
|
|
|
- _expression-field-extended-type_ ::= _designator_ `:`
|
|
_expression-extended-return-type_
|
|
- _proper-field-extended-type_ ::= _designator_ `:`
|
|
_proper-extended-return-type_
|
|
- _field-extended-type_ ::= _field-decl_
|
|
- _field-extended-type_ ::= _proper-field-extended-type_
|
|
- _proper-extended-return-type_ ::= `{` [_expression-field-extended-type_
|
|
`,`]\* _proper-extended-type_ [`,` _field-extended-type_]\* `}`
|
|
|
|
A struct literal of extended return types denotes a struct extended type whose
|
|
field names and their extended types are specified by the comma-separated field
|
|
extended types. To avoid formal ambiguity, this grammar rule requires at least
|
|
one of the field extended types to be proper.
|
|
|
|
> **Open question:** Should there be a way to specify symbolic or template phase
|
|
> in extended return types?
|
|
|
|
#### Deferred initialization from values and references
|
|
|
|
TODO: This section needs to be updated to reflect the addition of `-> val`
|
|
returns in
|
|
[proposal #5434](/proposals/p005434-ref-parameters-arguments-returns-and-val-returns.md).
|
|
This section could be replaced by a statement that initializing returns may be
|
|
replaced by value returns when that is safe and correct, moving much of this
|
|
content into a description of how value returns works.
|
|
|
|
Carbon also makes the evaluation of function calls and return statements tightly
|
|
linked in order to enable more efficiency improvements. It allows the actual
|
|
initialization performed by the `return` statement with its expression to be
|
|
deferred from within the body of the function to the caller initializer
|
|
expression if it can simply propagate a value or reference expression to the
|
|
caller that is guaranteed to be alive and available to the caller.
|
|
|
|
Consider the following code:
|
|
|
|
```carbon
|
|
fn SelectSecond(first: Point, second: Point, third: Point) -> Point {
|
|
return second;
|
|
}
|
|
|
|
fn UsePoint(p: Point);
|
|
|
|
fn F(p1: Point, p2: Point) {
|
|
UsePoint(SelectSecond(p2, p1, p2));
|
|
}
|
|
```
|
|
|
|
The call to `SelectSecond` must provide storage for a `Point` that can be
|
|
initialized. However, Carbon allows an implementation of the actual
|
|
`SelectSecond` function to not initialize this storage when it reaches
|
|
`return second`. The expression `second` is a name bound to the call's argument
|
|
value expression, and that value expression is necessarily valid in the caller.
|
|
Carbon in this case allows the implementation to merely communicate that the
|
|
returned expression is a name bound to a specific value expression argument to
|
|
the call, and the caller _if necessary_ should initialize the temporary storage.
|
|
This in turn allows the caller `F` to recognize that the value expression
|
|
argument (`p1`) is already valid to pass as the argument to `UsePoint` without
|
|
initializing the temporary storage from it and reading it back out of that
|
|
storage.
|
|
|
|
None of this impacts the type system and so an implementation can freely select
|
|
specific strategies here based on concrete types without harming generic code.
|
|
Even if generics were to be implemented without monomorphization, for example
|
|
dynamic dispatch of object-safe interfaces, there is a conservatively correct
|
|
strategy that will work for any type.
|
|
|
|
This freedom mirrors that of [input values](#value-expressions) where might be
|
|
implemented as either a reference or a copy without breaking genericity. Here
|
|
too, many small types will not need to be lazy and simply eagerly initialize the
|
|
temporary which is implemented as an actual machine register. But for large
|
|
types or ones with associated allocated storage, this can reliably avoid
|
|
extraneous memory allocations and other costs.
|
|
|
|
Note that this flexibility doesn't avoid the call expression materializing
|
|
temporary storage and providing it to the function. Whether the function needs
|
|
this storage is an implementation detail. It simply allows deferring an
|
|
important case of initializing that storage from a value or reference expression
|
|
already available in the caller to the caller so that it can identify cases
|
|
where that initialization is not necessary.
|
|
|
|
**References:** This addresses an issue-for-leads about
|
|
[reducing the potential copies incurred by returns](https://github.com/carbon-language/carbon-lang/issues/828).
|
|
|
|
#### Declared `returned` variable
|
|
|
|
The model of initialization of returns also facilitates the use of
|
|
[`returned var` declarations](control_flow/return.md#returned-var). These
|
|
directly observe the storage provided for initialization of a function's return.
|
|
|
|
## Extended types
|
|
|
|
We typically treat the category and type of an expression as independent
|
|
properties. However, in some cases we need to deal with them as an integrated
|
|
whole. The _extended type_ of an expression captures all of the information
|
|
about it that is visible to the type system, while abstracting away all other
|
|
information about it. Thus, extended types are a generalization of types: what
|
|
we conventionally call "types" are really the types of objects and values,
|
|
whereas extended types are the types of expressions and patterns. The type
|
|
`Core.ExtType` represents the type of an extended type constant, just as `type`
|
|
represents the type of an object type constant.
|
|
|
|
A _primitive extended type_ currently consists of a type, an expression
|
|
category, an expression phase, and optionally a constant value (which is present
|
|
if and only if the expression phase is not "runtime"). When dealing with
|
|
primitive extended types, which is the common case, we can treat each of those
|
|
properties as independent. For convenience, in this section we will use the
|
|
notation `<T, C, P, V>` to represent a primitive extended type with type `T`,
|
|
category `C`, phase `P` and value `V`, but this is not Carbon syntax.
|
|
|
|
Other extended types are called _composite extended types_, and there are two
|
|
kinds:
|
|
|
|
A _tuple extended type_ can be thought of as a tuple of extended types, just as
|
|
a tuple type can be thought of as a tuple of types. The extended type of a tuple
|
|
literal is a tuple extended type, whose elements are the extended types of the
|
|
literal elements.
|
|
|
|
> **TODO:** Extend this to support variadic extended types.
|
|
|
|
A _struct extended type_ can be thought of as a struct whose fields are extended
|
|
types, just as a struct type can be thought of as a struct whose fields are
|
|
types. The extended type of a struct literal is a struct extended type with the
|
|
same field names, whose values are the extended types of the corresponding
|
|
fields of the struct literal.
|
|
|
|
The _type component_ of an extended type is defined as follows:
|
|
|
|
- The type component of a primitive extended type `<T, C, P, V>` is `T`.
|
|
- The type component of a tuple extended type is a tuple of the type
|
|
components of its elements.
|
|
- The type component of a struct extended type is a struct whose field names
|
|
are the field names of the struct extended type and whose field types are
|
|
the type components of the corresponding elements.
|
|
|
|
The _category component_ and _phase component_ of an extended type are defined
|
|
likewise. The category component of a struct extended type is called a _struct
|
|
category_, and the category component of a tuple extended type is called a
|
|
_tuple category_.
|
|
|
|
The type of an expression is the type component of the expression's extended
|
|
type.
|
|
|
|
Evaluating an expression produces a _result_. It can be defined recursively in
|
|
terms of the expression's extended type:
|
|
|
|
- The result of an initializing expression is an
|
|
[initializing result](#initializing-results).
|
|
- The result of a value expression is a value.
|
|
- The result of a reference expression is a reference of the same kind.
|
|
- The result of an expression with tuple extended type is a tuple of results.
|
|
- The result of an expression with struct extended type is a struct of
|
|
results.
|
|
|
|
An expression and its result always have the same extended type.
|
|
|
|
The code that accesses the result of an expression is said to _consume_ that
|
|
result, and every primitive-extended-type result is consumed exactly once
|
|
(except in certain narrow contexts where the result is known not to be
|
|
initializing). If a result isn't explicitly accessed, such as when the
|
|
expression is used as a statement, it is said to be _discarded_, which consumes
|
|
it in the absence of an explicit consumer. Discarding an initializing result
|
|
materializes and then immediately destroys it. Discarding an entire ephemeral
|
|
reference destroys the object it refers to. Discarding a value or any other kind
|
|
of reference is a no-op.
|
|
|
|
### Initializing results
|
|
|
|
As discussed earlier, evaluation of an initializing expression takes as an input
|
|
the result location that it initializes, which is implicitly provided by the
|
|
context in which the evaluation takes place. In some cases, the context may
|
|
obtain the location from its own context, and so on. For example:
|
|
|
|
```carbon
|
|
class C {
|
|
private var i: i32;
|
|
|
|
fn Make() -> C {
|
|
return {.i = 0};
|
|
}
|
|
}
|
|
|
|
fn F() -> C {
|
|
return C.Make();
|
|
}
|
|
|
|
fn G() {
|
|
var c: C = F();
|
|
}
|
|
```
|
|
|
|
By default, a function call is an initializing expression, and a `return`
|
|
statement initializes the call's result location (which is passed as a hidden
|
|
output parameter). So when the declaration of `c` is evaluated, its storage is
|
|
implicitly passed into `F()` as an output parameter, which is initialized by the
|
|
`return` statement inside `F`. When that `return` statement is evaluated to
|
|
initialize the result location, it likewise implicitly passes the storage into
|
|
`C.Make()` as an output parameter, which is initialized by the `return`
|
|
statement inside `C.Make`. Finally, that `return` statement initializes the
|
|
result location (which is still the location of `c`'s storage) by
|
|
[direct initialization](#direct-initialization) from the value expression
|
|
`{.i = 0}`.
|
|
|
|
Notice that the implicit storage parameter propagates "backwards", into an
|
|
expression from the code that uses its result. In order to simplify the
|
|
description of the language, we usually won't explicitly discuss the result
|
|
locations of initializing expressions, or how they're propagated. Instead, this
|
|
propagation is encapsulated inside the _initializing result_, which is the
|
|
notional result of an initializing expression.
|
|
|
|
Whenever an initializing result is consumed, that implicitly means that the
|
|
consumer passes a result location into the evaluation of the initializing
|
|
expression. The source of that location depends on the consumer:
|
|
|
|
- If the consumer is a temporary materialization conversion, the result
|
|
location is newly-allocated temporary storage (which the consumer may
|
|
subsequently lifetime-extend to durable storage).
|
|
- If the consumer is a `return` statement, and the initializing result
|
|
corresponds to an initializing sub-extended-type of the function's return
|
|
extended type, the result location is the implicit output parameter
|
|
corresponding to that initializing sub-extended-type.
|
|
|
|
### Extended type conversions
|
|
|
|
A conversion between extended types can be broken down into up to three steps:
|
|
type conversion, category conversion, and phase conversion. These convert the
|
|
extended type to a particular target type, category, and phase component
|
|
(respectively). These steps aren't fully orthogonal: type conversions can change
|
|
the category and phase components as a byproduct, and category conversions can
|
|
change the phase component. However, category conversions can't change the type
|
|
component, and phase conversions can't change either of the other two, so
|
|
converting the type, then category, then phase, ensures that we converge on the
|
|
desired result.
|
|
|
|
Any of these steps may be omitted, depending on whether the context imposes
|
|
requirements on the corresponding component. Most commonly, an operand position
|
|
requires its operand to have a primitive extended type with a particular
|
|
category, usually with a particular type, and sometimes with a particular phase.
|
|
|
|
Phase conversions cannot change the extended type structure; they can only apply
|
|
primitive phase conversions to primitive sub-extended-types. Type and category
|
|
conversions are more complex, and are covered in the next two sections.
|
|
|
|
Note that these rules will implicitly convert between primitive and composite
|
|
extended types in both directions (except that a composite containing references
|
|
cannot be converted to a primitive extended type). As a result, although the
|
|
difference between primitive and composite extended types is observable by way
|
|
of overloading, it can't reliably carry any higher-level meaning, and should be
|
|
used only as an optimization tool.
|
|
|
|
Note that this section describes the _logical structure_ of extended type
|
|
conversions. As such, it primarily describes them "breadth-first", as a sequence
|
|
of operations that each applies to the whole expression by recursively operating
|
|
on its parts. However, the _physical execution_ of these conversions is actually
|
|
depth-first, applying as many operations as possible to a minimal subexpression
|
|
before moving on to the next one. The details of that process are described
|
|
[here](pattern_matching.md#evaluation-order).
|
|
|
|
#### Type conversions
|
|
|
|
See [here](expressions/implicit_conversions.md) for overall information about
|
|
type conversions. Conversions involving struct, tuple, and array types are
|
|
described here because of their unique interactions with extended types of
|
|
expressions.
|
|
|
|
> **TODO:** A forthcoming proposal is expected to update the type conversion
|
|
> interfaces to permit user-defined conversions to depend on the extended type
|
|
> of the input, and customize the extended type of the output. Once that is
|
|
> done, these "built in" conversions should be presented as implementations of
|
|
> those interfaces, possibly with some "magic" for things like introspecting on
|
|
> struct field names.
|
|
|
|
Each of the conversions described in this section is explicit if and only if it
|
|
invokes another explicit type conversion. Otherwise, it is implicit.
|
|
|
|
A type conversion of an expression with primitive extended type to a
|
|
[compatible type](classes.md#compatible-types) just re-interprets
|
|
the expression's result with a new type, so it requires no run-time work, and
|
|
has the same category as the input expression.
|
|
|
|
A result `source` that has a struct type can be converted to a struct type
|
|
`Dest` if they have the same set of field names:
|
|
|
|
- If the type of `source` is `Dest`, return `source`.
|
|
- If `source` is a struct result, for each field name `F` in `Dest`,
|
|
type-convert `source.F` to `Dest.F`. Return a struct result where each field
|
|
`F` is set to the result of the corresponding conversion.
|
|
- If `source` is a primitive result, convert it to a struct result by
|
|
[extended type decomposition](#category-conversions), and then type-convert
|
|
the result to `Dest` and return the result.
|
|
|
|
Note that the sub-conversions invoked here are not necessarily defined; if so,
|
|
the conversion itself is not defined.
|
|
|
|
There is a conversion to a class type `Dest` from a result `source` that has a
|
|
struct type, if there is a conversion from `source` to a struct type that has
|
|
the same field names as `Dest` (including a `.base` field if `Dest` is a derived
|
|
class), with the same types, in the same order. The conversion type-converts
|
|
`source` to that struct type, category-converts that to an initializing
|
|
expression of the struct type, and then reinterprets it as an initializing
|
|
expression of `Dest` (which is layout-compatible with the struct type by
|
|
construction).
|
|
|
|
Note that some fields of an object may be initialized directly by the evaluation
|
|
of the source expression, while others may be initialized by the conversions
|
|
described here. The conversions initialize fields in their declaration order,
|
|
but the evaluation of the source expression always happens before any of the
|
|
conversions, and happens in the source expression's lexical order, so the fields
|
|
of an object are not necessarily initialized in declaration order.
|
|
|
|
Conversions between tuple types are defined in the same way, treating tuples as
|
|
structs that have fields named `.0`, `.1`, etc, in numerical order.
|
|
|
|
There is a conversion to `array(T, N)` from any expression with a tuple extended
|
|
type of exactly `N` elements, whose type components are convertible to `T`. The
|
|
conversion is an initializing expression, which type-converts each source
|
|
element to `T`, and initializes the corresponding array element from the result
|
|
of that conversion.
|
|
|
|
#### Category conversions
|
|
|
|
_Extended type composition_ converts an expression of composite extended type
|
|
with consistent category to a primitive extended type as follows (where `min` as
|
|
applied to phases uses the ordering "runtime" < "symbolic" < "template"):
|
|
|
|
- An expression of tuple extended type
|
|
`(<T1, C, P1, V1>, <T2, C, P2, V2>, ... <TN, C, PN, VN>)` can be converted
|
|
to a primitive extended type
|
|
`<(T1, T2, ..., TN), C, min(P1, P2, ..., PN), (V1, V2, ... VN)>`.
|
|
- An expression of struct extended type `{.a = <Ta, C, Pa, Va>, .b = <Tb, C,
|
|
Pb, Vb>, ... .z = <Tz, C, Pz, Vz>}` can be converted to a primitive extended
|
|
type `<{.a = Ta, .b = Tb, ... .z = Tz}, C, min(Pa, Pb, ... Pz), {.a = Va,
|
|
.b = Vb, ... .z = Vz}>`.
|
|
|
|
When `C` is "value", composition forms a value representation of the aggregate
|
|
from value representations of the elements. When `C` is "initializing", it
|
|
transforms initializing expressions for each element into a single initializing
|
|
expression that initializes the whole aggregate. `C` cannot be a reference
|
|
category, because an aggregate of references to independent objects can't be
|
|
replaced by a reference to a single aggregate object in a single step.
|
|
|
|
_Extended type decomposition_ is the inverse of extended type composition. It
|
|
converts an expression with primitive extended type to a composite extended type
|
|
as follows:
|
|
|
|
- An expression with primitive extended type `<(T0, T1, ..., TN), C, P, V>`
|
|
can be converted to a tuple extended type
|
|
`(<T0, CC, P, V.0>, <T1, CC, P, V.1>, ... <TN, CC, P, V.N>)`.
|
|
- An expression with primitive extended type
|
|
`<{.a = Ta, .b = Tb, ... .z = Tz}, C, P, V>` can be converted to a struct
|
|
extended type
|
|
`{.a = <Ta, CC, P, V.a>, .b = <Tb, CC, P, V.b>, ... .z = <Tz, CC, P, V.z>}`.
|
|
|
|
The category `CC` of the resulting sub-extended-types is the same as `C`, with
|
|
two exceptions:
|
|
|
|
- If `C` is "durable entire reference", `CC` will be "durable non-entire
|
|
reference", because the sub-extended-types don't refer to complete objects.
|
|
This doesn't apply to ephemeral entire references, because in that case
|
|
extended type decomposition implicitly ends the lifetime of the original
|
|
aggregate, promoting its elements to complete objects with independent
|
|
lifetimes.
|
|
- If `C` is "initializing", the original expression is materialized before it
|
|
is decomposed, so `CC` will be "ephemeral entire reference".
|
|
|
|
By convention, extended type decomposition is a no-op when applied to an
|
|
expression with struct or tuple extended type.
|
|
|
|
_Category conversion_ converts an expression to have a given category component
|
|
without changing its type component. The conversion works by combining extended
|
|
type composition and decomposition with primitive category conversions, and is
|
|
defined recursively:
|
|
|
|
- If the target category component is a tuple, the source extended type must
|
|
have a type component that is a tuple type with the same arity. Convert the
|
|
source to a tuple extended type by extended type decomposition, and then
|
|
category-convert each source sub-extended-type to the corresponding target
|
|
sub-category.
|
|
- If the target category component is a struct, the source extended type must
|
|
have a type component that is a struct type with the same set of field names
|
|
in the same order. Convert the source to a struct extended type by extended
|
|
type decomposition, and then category-convert each source sub-extended-type
|
|
to the corresponding target sub-category.
|
|
- If the target category is a primitive category `C`:
|
|
- If the source extended type is primitive, convert to `C` by applying
|
|
primitive category conversions.
|
|
- If the source extended type is composite and `C` is a reference
|
|
category, category-convert the source extended type to "initializing",
|
|
and then convert the result to `C` by applying primitive category
|
|
conversions.
|
|
- If the source extended type is composite and `C` is not a reference
|
|
category, category-convert each source sub-extended-type to `C`, and
|
|
then convert the aggregate result of these conversions to `C` by
|
|
extended type composition.
|
|
|
|
## Pointers
|
|
|
|
Pointers in Carbon are the primary mechanism for _indirect access_ to storage
|
|
containing some value. Dereferencing a pointer is one of the primary ways to
|
|
form a [_durable reference expression_](#durable-reference-expressions).
|
|
|
|
Carbon pointers are heavily restricted compared to C++ pointers -- they cannot
|
|
be null and they cannot be indexed or have pointer arithmetic performed on them.
|
|
In some ways, this makes them more similar to references in C++, but they retain
|
|
the essential aspect of a pointer that they syntactically distinguish between
|
|
the point*er* and the point*ee*.
|
|
|
|
Carbon will still have mechanisms to achieve the equivalent behaviors as C++
|
|
pointers. Optional pointers are expected to serve nullable use cases. Slice or
|
|
view style types are expected to provide access to indexable regions. And even
|
|
raw pointer arithmetic is expected to be provided at some point, but through
|
|
specialized constructs given the specialized nature of these operations.
|
|
|
|
**Future work:** Add explicit designs for these use cases and link to them here.
|
|
|
|
### Reference types
|
|
|
|
TODO: This section needs to be updated to reflect
|
|
[proposal #5434](/proposals/p005434-ref-parameters-arguments-returns-and-val-returns.md).
|
|
|
|
Unlike C++, Carbon does not currently have reference types. The only form of
|
|
indirect access are pointers. There are a few aspects to this decision that need
|
|
to be separated carefully from each other as the motivations and considerations
|
|
are different.
|
|
|
|
First, Carbon has only a single fundamental construct for indirection because
|
|
this gives it a single point that needs extension and configuration if and when
|
|
we want to add more powerful controls to the indirect type system such as
|
|
lifetime annotations or other safety or optimization mechanisms. The designs
|
|
attempts to identify a single, core indirection tool and then layer other
|
|
related use cases on top. This is motivated by keeping the language scalable as
|
|
it evolves and reducing the huge explosion of complexity that C++ sees due to
|
|
having a large space here. For example, when there are N > 1 ways to express
|
|
indirection equivalently and APIs want to accept any one of them across M
|
|
different parameters they can end up with N \* M combinations.
|
|
|
|
Second, with pointers, Carbon's indirection mechanism retains the ability to
|
|
refer distinctly to the point*er* and the point*ee* when needed. This ends up
|
|
critical for supporting rebinding and so without this property more permutations
|
|
of indirection would likely emerge.
|
|
|
|
Third, Carbon doesn't provide a straightforward way to avoid the syntactic
|
|
distinction between indirect access and direct access.
|
|
|
|
For a full discussion of the tradeoffs of these design decisions, see the
|
|
alternatives considered section of [P2006]:
|
|
|
|
- [References in addition to pointers](/proposals/p002006-values-variables-pointers-and-references.md#references-in-addition-to-pointers)
|
|
- [Syntax-free or automatic dereferencing](/proposals/p002006-values-variables-pointers-and-references.md#syntax-free-or-automatic-dereferencing)
|
|
- [Exclusively using references](/proposals/p002006-values-variables-pointers-and-references.md#exclusively-using-references)
|
|
|
|
### Pointer syntax
|
|
|
|
The type of a pointer to a type `T` is written with a postfix `*` as in `T*`.
|
|
Dereferencing a pointer is a [_reference expression_] and is written with a
|
|
prefix `*` as in `*p`:
|
|
|
|
```carbon
|
|
var i: i32 = 42;
|
|
var p: i32* = &i;
|
|
|
|
// Form a reference expression `*p` and assign `13` to the referenced storage.
|
|
*p = 13;
|
|
```
|
|
|
|
This syntax is chosen specifically to remain as similar as possible to C++
|
|
pointer types as they are commonly written in code and are expected to be
|
|
extremely common and a key anchor of syntactic similarity between the languages.
|
|
The different alternatives and tradeoffs for this syntax issue were discussed
|
|
extensively in [#523] and are summarized in the
|
|
[proposal](/proposals/p002006-values-variables-pointers-and-references.md#alternative-pointer-syntaxes).
|
|
|
|
[#523]: https://github.com/carbon-language/carbon-lang/issues/523
|
|
|
|
Carbon also supports an infix `->` operation, much like C++. However, Carbon
|
|
directly defines this as an exact rewrite to `*` and `.` so that `p->member`
|
|
becomes `(*p).member` for example. This means there is no overloaded or
|
|
customizable `->` operator in Carbon the way there is in C++. Instead,
|
|
customizing the behavior of `*p` in turn customizes the behavior of `p->`.
|
|
|
|
**Future work:** As [#523] discusses, one of the primary challenges of the C++
|
|
syntax is the composition of a prefix dereference operation and other postfix or
|
|
infix operations, especially when chained together such as a classic C++
|
|
frustrations of mixes of dereference and indexing: `(*(*p)[42])[13]`. Where
|
|
these compositions are sufficiently common to create ergonomic problems, the
|
|
plan is to introduce custom syntax analogous to `->` that rewrites down to the
|
|
grouped dereference. However, nothing beyond `->` itself is currently provided.
|
|
Extending this, including the exact design and scope of extension desired, is a
|
|
future work area.
|
|
|
|
### Dereferencing customization
|
|
|
|
Carbon should support user-defined pointer-like types such as _smart pointers_
|
|
using a similar pattern as operator overloading or other expression syntax. That
|
|
is, it should rewrite the expression into a member function call on an
|
|
interface. Types can then implement this interface to expose pointer-like
|
|
_user-defined dereference_ syntax.
|
|
|
|
The interface might look like:
|
|
|
|
```carbon
|
|
interface Pointer {
|
|
let ValueT: Type;
|
|
fn Dereference(self) -> ValueT*;
|
|
}
|
|
```
|
|
|
|
Here is an example using a hypothetical `TaggedPtr` that carries some extra
|
|
integer tag next to the pointer it emulates:
|
|
|
|
```carbon
|
|
class TaggedPtr(T: Type) {
|
|
var tag: Int32;
|
|
var ptr: T*;
|
|
}
|
|
external impl [T: Type] TaggedPtr(T) as Pointer {
|
|
let ValueT: T;
|
|
fn Dereference(self) -> T* { return self.ptr; }
|
|
}
|
|
|
|
fn Test(arg: TaggedPtr(T), dest: TaggedPtr(TaggedPtr(T))) {
|
|
**dest = *arg;
|
|
*dest = arg;
|
|
}
|
|
```
|
|
|
|
There is one tricky aspect of this. The function in the interface which
|
|
implements a pointer-like dereference must return a raw pointer which the
|
|
language then actually dereferences to form a reference expression similar to
|
|
that formed by `var` declarations. This interface is implemented for normal
|
|
pointers as a no-op:
|
|
|
|
```carbon
|
|
impl [T: Type] T* as Pointer {
|
|
let ValueT: Type = T;
|
|
fn Dereference(self) -> T* { return self; }
|
|
}
|
|
```
|
|
|
|
Dereference expressions such as `*x` are syntactically rewritten to use this
|
|
interface to get a raw pointer and then that raw pointer is dereferenced. If we
|
|
imagine this language level dereference to form a reference expression as a
|
|
unary `deref` operator, then `(*x)` becomes
|
|
`(deref (x.(Pointer.Dereference)()))`.
|
|
|
|
Carbon will also use a simple syntactic rewrite for implementing `x->Method()`
|
|
as `(*x).Method()` without separate or different customization.
|
|
|
|
## `const`-qualified types
|
|
|
|
Carbon provides the ability to qualify a type `T` with the keyword `const` to
|
|
get a `const`-qualified type: `const T`. This is exclusively an API-subsetting
|
|
feature in Carbon -- for more fundamentally "immutable" use cases, value
|
|
expressions and bindings should be used instead. Pointers to `const`-qualified
|
|
types in Carbon provide access to an object with an API subset that can help
|
|
model important requirements like ensuring usage is exclusively by way of a
|
|
_thread-safe_ interface subset of an otherwise _thread-compatible_ type.
|
|
|
|
Note that `const T` is a type qualification and is generally orthogonal to
|
|
expression categories or what form of pattern is used, including for object
|
|
parameters. Notionally, it can occur both with `ref` and value object
|
|
parameters. However, on value patterns, it is redundant as there is no
|
|
meaningful distinction between a value expression of type `T` and type
|
|
`const T`. For example, given a type and methods:
|
|
|
|
```carbon
|
|
class X {
|
|
fn Method(self);
|
|
fn ConstMethod(self: const Self);
|
|
fn RefMethod(ref self);
|
|
fn RefConstMethod(ref self: const Self);
|
|
}
|
|
```
|
|
|
|
The methods can be called on different kinds of expressions according to the
|
|
following table:
|
|
|
|
| Expression category: | `let x: X` <br/> (value) | `let x: const X` <br/> (const value) | `var x: X` <br/> (reference) | `var x: const X` <br/> (const reference) |
|
|
| -------------------: | ------------------------ | ------------------------------------ | ---------------------------- | ---------------------------------------- |
|
|
| `x.Method();` | ✅ | ✅ | ✅ | ✅ |
|
|
| `x.ConstMethod();` | ✅ | ✅ | ✅ | ✅ |
|
|
| `x.RefMethod();` | ❌ | ❌ | ✅ | ❌ |
|
|
| `x.RefConstMethod()` | ❌ | ❌ | ✅ | ✅ |
|
|
|
|
The `const T` type has the same representation as `T` with the same field names,
|
|
but all of its field types are also `const`-qualified. Other than fields, all
|
|
other members `T` are also members of `const T`, and impl lookup ignores the
|
|
`const` qualification. There is an implicit conversion from `T` to `const T`,
|
|
but not the reverse. Conversion of reference expressions to value expressions is
|
|
defined in terms of `const T` reference expressions to `T` value expressions.
|
|
|
|
It is expected that `const T` will largely occur as part of a
|
|
[pointer](#pointers), as the express purpose is to form reference expressions.
|
|
The precedence rules are even designed for this common case, `const T*` means
|
|
`(const T)*`, or a pointer-to-const. Carbon will support conversions between
|
|
pointers to `const`-qualified types that follow the same rules as used in C++ to
|
|
avoid inadvertent loss of `const`-qualification.
|
|
|
|
The syntax details of `const` are also covered in the
|
|
[type operators](/docs/design/expressions/type_operators.md) documentation.
|
|
|
|
## Lifetime overloading
|
|
|
|
One potential use case that is not obviously or fully addressed by these designs
|
|
in Carbon is overloading function calls by observing the lifetime of arguments.
|
|
The use case here would be selecting different implementation strategies for the
|
|
same function or operation based on whether an argument lifetime happens to be
|
|
ending and viable to move-from.
|
|
|
|
Carbon currently intentionally leaves this use case unaddressed. There is a
|
|
fundamental scaling problem in this style of overloading: it creates a
|
|
combinatorial explosion of possible overloads similar to other permutations of
|
|
indirection models. Consider a function with N parameters that would benefit
|
|
from lifetime overloading. If each parameter benefits _independently_ from the
|
|
others, as is commonly the case, we would need 2<sup>N</sup> overloads to
|
|
express all the possibilities.
|
|
|
|
Carbon will initially see if code can be designed without this facility. Some of
|
|
the tools needed to avoid it are suggested above such as the
|
|
[consuming](#consuming-function-parameters) input pattern. But it is possible
|
|
that more will be needed in practice. It would be good to identify the specific
|
|
and realistic Carbon code patterns that cannot be expressed with the tools in
|
|
this proposal in order to motivate a minimal extension. Some candidates based on
|
|
functionality already proposed here or for [classes](/docs/design/classes.md):
|
|
|
|
- Allow overloading between `ref self` and `self` in methods. This is among
|
|
the most appealing as it _doesn't_ have the combinatorial explosion. But it
|
|
is also very limited as it only applies to the implicit object parameter.
|
|
- Allow overloading between `var` and non-`var` parameters.
|
|
- Allow overloading between `ref` and non-`ref` parameters in general.
|
|
|
|
Perhaps more options will emerge as well. Again, the goal isn't to completely
|
|
preclude pursuing this direction, but instead to try to ensure it is only
|
|
pursued based on a real and concrete need, and the minimal extension is adopted.
|
|
|
|
## Value representation and customization
|
|
|
|
The representation of a value expression is especially important because it
|
|
forms the calling convention used for the vast majority of function parameters
|
|
-- function inputs. Given this importance, it's important that it is predictable
|
|
and customizable by the value's type. Similarly, while Carbon code must be
|
|
correct with either a copy or a reference-based implementation, we want which
|
|
implementation strategy is used to be a predictable and customizable property of
|
|
the type of a value.
|
|
|
|
A type can optionally control its value representation using a custom syntax
|
|
similar to customizing its [destructor](/docs/design/classes.md#destructors).
|
|
This syntax sets the representation to some type uses a keyword `value_rep` and
|
|
can appear where a member declaration would be valid within the type:
|
|
|
|
```carbon
|
|
class SomeType {
|
|
value_rep = RepresentationType;
|
|
}
|
|
```
|
|
|
|
**Open question:** The syntax for this is just placeholder, using a placeholder
|
|
keyword. It isn't final at all and likely will need to change to read well.
|
|
|
|
The provided representation type must be one of the following:
|
|
|
|
- `const Self` -- this forces the use of a _copy_ of the object.
|
|
- `const ref` -- this forces the use of a [_pointer_](#pointers) to the
|
|
original object, but with the `const` API subset.
|
|
- A custom type that is not `Self`, `const Self`, or a pointer to either.
|
|
|
|
If the representation is `const Self` or `const ref`, then the type fields will
|
|
be accessible as [_value expressions_](#value-expressions) using the normal
|
|
member access syntax for value expressions of a type. These will be implemented
|
|
by either accessing a copy of the object in the non-pointer case or a pointer to
|
|
the original object in the pointer case. A representation of `const Self`
|
|
requires copying to be valid for the type. This provides the builtin
|
|
functionality but allows explicitly controlling which representation should be
|
|
used.
|
|
|
|
If no customization is provided, the implementation will select one based on a
|
|
set of heuristics. Some examples:
|
|
|
|
- Non-copyable types and polymorphic types would use a `const ref`.
|
|
- Small objects that are trivially copied in a machine register would use
|
|
`const Self`.
|
|
|
|
When a custom type is provided, it must not be `Self`, `const Self`, or a
|
|
pointer to either. The type provided will be used on function call boundaries
|
|
and as the implementation representation for value bindings and other value
|
|
expressions referencing an object of the type. A specifier of `value_rep = T;`
|
|
will require that the type containing that specifier satisfies the constraint
|
|
`impls ReferenceImplicitAs where .T = T` using the following interface:
|
|
|
|
```carbon
|
|
interface ReferenceImplicitAs {
|
|
let T: type;
|
|
fn Convert(ref self: const Self) -> T;
|
|
}
|
|
```
|
|
|
|
Converting a reference expression into a value expression for such a type calls
|
|
this customization point to form a representation object from the original
|
|
reference expression.
|
|
|
|
When using a custom representation type in this way, no fields are accessible
|
|
through a value expression. Instead, only methods can be called using member
|
|
access, as they simply bind the value expression to the `self` parameter.
|
|
However, one important method can be called -- `.(ImplicitAs(T).Convert)()`.
|
|
This implicitly converting a value expression for the type into its custom
|
|
representation type. The customization of the representation above and
|
|
`impls ReferenceImplicitAs where .T = T` causes the class to have a builtin
|
|
`impl as ImplicitAs(T)` which converts to the representation type as a no-op,
|
|
exposing the object created by calling `ReferenceImplicitAs.Convert` on the
|
|
original reference expression, and preserved as a representation of the value
|
|
expression.
|
|
|
|
Here is a more complete example of code using these features:
|
|
|
|
```carbon
|
|
class StringView {
|
|
private var data_ptr: Char*;
|
|
private var size: i64;
|
|
|
|
fn Make(data_ptr: Char*, size: i64) -> StringView {
|
|
return {.data_ptr = data_ptr, .size = size};
|
|
}
|
|
|
|
// A typical readonly view of a string API...
|
|
fn ExampleMethod(self) { ... }
|
|
}
|
|
|
|
class String {
|
|
// Customize the value representation to be `StringView`.
|
|
value_rep = StringView;
|
|
|
|
private var data_ptr: Char*;
|
|
private var size: i64;
|
|
|
|
private var capacity: i64;
|
|
|
|
impl as ReferenceImplicitAs where .T = StringView {
|
|
fn Op(ref self: const Self) -> StringView {
|
|
// Because this is called on the String object prior to it becoming
|
|
// a value, we can access an SSO buffer or other interior pointers
|
|
// of `self`.
|
|
return StringView.Make(self.data_ptr, self.size);
|
|
}
|
|
}
|
|
|
|
// We can directly declare methods that take `self` as a `StringView` which
|
|
// will cause the caller to implicitly convert value expressions to
|
|
// `StringView` prior to calling.
|
|
fn ExampleMethod(self: StringView) { self.ExampleMethod(); }
|
|
|
|
// Or we can use a value binding for `self` much like normal, but the
|
|
// implementation will be constrained because of the custom value rep.
|
|
fn ExampleMethod2(self: String) {
|
|
// Error due to custom value rep:
|
|
self.data_ptr;
|
|
|
|
// Fine, this uses the builtin `ImplicitAs(StringView)`.
|
|
(self as StringView).ExampleMethod();
|
|
}
|
|
|
|
// Note that even though the `Self` type is `const` qualified here, this
|
|
// cannot be called on a `String` value! That would require us to convert to a
|
|
// `StringView` that does not track the extra data member.
|
|
fn Capacity(ref self: const Self) -> i64 {
|
|
return self.capacity;
|
|
}
|
|
}
|
|
```
|
|
|
|
It is important to note that the _representation_ type of a value expression is
|
|
just its representation and does not impact the name lookup or type. Name lookup
|
|
and `impl` search occur for the same type regardless of the expression category.
|
|
But once a particular method or function is selected, an implicit conversion can
|
|
occur from the original type to the representation type as part of the parameter
|
|
or receiver type. In fact, this conversion is the _only_ operation that can
|
|
occur for a value whose type has a customized value representation.
|
|
|
|
The example above also demonstrates the fundamental tradeoff made by customizing
|
|
the value representation of a type in this way. While it provides a great deal
|
|
of control, it may result in some surprising limitations. Above, a method that
|
|
is classically available on a C++ `const std::string&` like querying the
|
|
capacity cannot be implemented with the customized value representation because
|
|
it loses access to this additional state. Carbon allows type authors to make an
|
|
explicit choice about whether they want to work with a restricted API and
|
|
leverage a custom value representation or not.
|
|
|
|
**Open question:** Beyond the specific syntax used where we currently have a
|
|
placeholder `value_rep = T;`, we need to explore exactly what the best
|
|
relationship is with the customization point. For example, should this syntax
|
|
immediately forward declare `impl as ReferenceImplicitAs where .T = T`, thereby
|
|
allowing an out-of-line definition of the `Convert` method and `... where _` to
|
|
pick up the associated constant from the syntax. Alternatively, the syntactic
|
|
marker might be integrated into the `impl` declaration for `ReferenceImplicitAs`
|
|
itself.
|
|
|
|
## Alternatives considered
|
|
|
|
- [No `var` introducer keyword](/proposals/p000339-var-statement.md#no-var-introducer-keyword)
|
|
- [Name of the `var` statement introducer](/proposals/p000339-var-statement.md#name-of-the-var-statement-introducer)
|
|
- [Colon between type and identifier](/proposals/p000339-var-statement.md#colon-between-type-and-identifier)
|
|
- [Type elision](/proposals/p000339-var-statement.md#type-elision)
|
|
- [Type ordering](/proposals/p000618-var-ordering.md#type-ordering)
|
|
- [Elide the type instead of using `auto`](/proposals/p000851-variable-type-inference.md#elide-the-type-instead-of-using-auto)
|
|
- [Value expression escape hatches](/proposals/p002006-values-variables-pointers-and-references.md#value-expression-escape-hatches)
|
|
- [References in addition to pointers](/proposals/p002006-values-variables-pointers-and-references.md#references-in-addition-to-pointers)
|
|
- [Syntax-free or automatic dereferencing](/proposals/p002006-values-variables-pointers-and-references.md#syntax-free-or-automatic-dereferencing)
|
|
- [Exclusively using references](/proposals/p002006-values-variables-pointers-and-references.md#exclusively-using-references)
|
|
- [Alternative pointer syntaxes](/proposals/p002006-values-variables-pointers-and-references.md#alternative-pointer-syntaxes)
|
|
- [Alternative syntaxes for locals](/proposals/p002006-values-variables-pointers-and-references.md#alternative-syntaxes-for-locals)
|
|
- [Mixed expression categories](/proposals/p005545-expression-form-basics.md#mixed-expression-categories)
|
|
- [Don't implicitly convert to less-primitive forms](/proposals/p005545-expression-form-basics.md#dont-implicitly-convert-to-less-primitive-forms)
|
|
- [Use `exprtype` and `expr` keywords](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#use-exprtype-and-expr-keywords)
|
|
|
|
## References
|
|
|
|
- [Proposal #257: Initialization of memory and values][#257]
|
|
- [Proposal #339: `var` statement][#339]
|
|
- [Proposal #618: `var` ordering][#618]
|
|
- [Proposal #851: auto keyword for vars][#851]
|
|
- [Proposal #2006: Values, variables, and pointers][#2006]
|
|
- [Proposal #5545: Expression form basics][#5545]
|
|
- [Proposal #7254: Replace `:!` and `:?` with keywords and contextual
|
|
defaults][#7254]
|
|
|
|
[#257]: /proposals/p000257-initialization-of-memory-and-variables.md
|
|
[#339]: /proposals/p000339-var-statement.md
|
|
[#618]: /proposals/p000618-var-ordering.md
|
|
[#851]: /proposals/p000851-variable-type-inference.md
|
|
[#2006]: /proposals/p002006-values-variables-pointers-and-references.md
|
|
[#5545]: /proposals/p005545-expression-form-basics.md
|
|
[#7254]: /proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md
|