ref parameters, arguments, returns and val returns (#5434)

- A parameter binding can be marked `ref` instead of `var` or the
default. It will bind to reference argument expressions in the caller
and produces a reference expression in the callee.
- Unlike pointers, a `ref` binding can't be rebound to a different
object.
- This replaces `addr`, and is not restricted to the `self` parameter.
- A `ref` binding, like a value binding, can't be used in fields of
classes or structs.
- When calling functions, arguments to non-`self` `ref` parameters are
also marked with `ref`.
- The return of a function can optionally be marked `ref`, `val`, or
`var`. These control the category of the call expression invoking the
function, and how the return expression is returned.
- These may be mixed for functions returning tuple or struct forms.
-   The address of a `ref` binding is `nocapture` and `noalias`.
- We mark parameters of a function that may be referenced by the return
value with `bound`.

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
josh11b
2025-08-01 17:43:41 +00:00
committed by GitHub
co-authored by Josh L Geoff Romer Chandler Carruth
parent e3a366f1c3
commit ac98870e67
3 changed files with 1701 additions and 35 deletions
+17 -27
View File
@@ -40,18 +40,8 @@ implement that interface, or to method calls on `IndexWith` otherwise.
`IndirectIndexWith` provides a final blanket `impl` of `IndexWith`, so a type
can implement at most one of those two interfaces.
The `Addr` methods of these interfaces, which are used to form durable reference
expressions on indexing, must return a pointer and work similarly to the
[pointer dereference customization interface](/docs/design/values.md#dereferencing-customization).
The returned pointer is then dereferenced by the language to form the reference
expression referring to the pointed-to object. These methods must return a raw
pointer, and do not automatically chain with customized dereference interfaces.
**Open question:** It's not clear that the lack of chaining is necessary, and it
might be more expressive for the pointer type returned by the `Addr` methods to
be an associated facet with a default to allow types to produce custom
pointer-like types on their indexing boundary and have them still be
automatically dereferenced.
The `Ref` methods of these interfaces, which are used to form durable reference
expressions on indexing, must return by `ref`.
## Details
@@ -64,13 +54,13 @@ Its semantics are defined in terms of the following interfaces:
```
interface IndexWith(SubscriptType:! type) {
let ElementType:! type;
fn At[self: Self](subscript: SubscriptType) -> ElementType;
fn Addr[addr self: Self*](subscript: SubscriptType) -> ElementType*;
fn At[bound self: Self](subscript: SubscriptType) -> val ElementType;
fn Ref[bound ref self: Self](subscript: SubscriptType) -> ref ElementType;
}
interface IndirectIndexWith(SubscriptType:! type) {
require Self impls IndexWith(SubscriptType);
fn Addr[self: Self](subscript: SubscriptType) -> ElementType*;
fn Ref[bound self: Self](subscript: SubscriptType) -> ref ElementType;
}
```
@@ -79,11 +69,11 @@ rewritten based on the expression category of _lhs_ and whether `T` is known to
implement `IndirectIndexWith(I)`:
- If `T` implements `IndirectIndexWith(I)`, the expression is rewritten to
"`*((` _lhs_ `).(IndirectIndexWith(I).Addr)(` _index_ `))`".
"`(` _lhs_ `).(IndirectIndexWith(I).Ref)(` _index_ `)`".
- Otherwise, if _lhs_ is a
[_durable reference expression_](/docs/design/values.md#durable-reference-expressions),
the expression is rewritten to "`*((` _lhs_ `).(IndexWith(I).Addr)(` _index_
`))`".
the expression is rewritten to "`(` _lhs_ `).(IndexWith(I).Ref)(` _index_
`)`".
- Otherwise, the expression is rewritten to "`(` _lhs_ `).(IndexWith(I).At)(`
_index_ `)`".
@@ -93,18 +83,18 @@ implement `IndirectIndexWith(I)`:
final impl forall
[SubscriptType:! type, T:! IndirectIndexWith(SubscriptType)]
T as IndexWith(SubscriptType) {
let ElementType:! type = T.(IndirectIndexWith(SubscriptType)).ElementType;
fn At[self: Self](subscript: SubscriptType) -> ElementType {
return *(self.(IndirectIndexWith(SubscriptType).Addr)(index));
where ElementType = T.(IndirectIndexWith(SubscriptType).ElementType);
fn At[bound self: Self](subscript: SubscriptType) -> val ElementType {
return self.(IndirectIndexWith(SubscriptType).Ref)(index);
}
fn Addr[addr self: Self*](subscript: SubscriptType) -> ElementType* {
return self->(IndirectIndexWith(SubscriptType).Addr)(index);
fn Ref[bound ref self: Self](subscript: SubscriptType) -> ref ElementType {
return self.(IndirectIndexWith(SubscriptType).Ref)(index);
}
}
```
Thus, a type that implements `IndirectIndexWith` need not, and cannot, provide
its own definitions of `IndexWith.At` and `IndexWith.Addr`.
its own definitions of `IndexWith.At` and `IndexWith.Ref`.
### Examples
@@ -114,8 +104,8 @@ An array type could implement subscripting like so:
class Array(template T:! type) {
impl as IndexWith(like i64) {
let ElementType:! type = T;
fn At[self: Self](subscript: i64) -> T;
fn Addr[addr self: Self*](subscript: i64) -> T*;
fn At[bound self: Self](subscript: i64) -> val T;
fn Ref[bound ref self: Self](subscript: i64) -> ref T;
}
}
```
@@ -126,7 +116,7 @@ And a type such as `std::span` could look like this:
class Span(T:! type) {
impl as IndirectIndexWith(like i64) {
let ElementType:! type = T;
fn Addr[self: Self](subscript: i64) -> T*;
fn Ref[bound ref self: Self](subscript: i64) -> ref T;
}
}
```
+17 -8
View File
@@ -415,9 +415,9 @@ 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 Self*` style representation (to
> prevent slicing) or it needs to have a model for the semantics when a
> different value representation is used.
> 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
@@ -560,6 +560,12 @@ functions with a `()` return type for the purpose of expression categories.
#### Deferred initialization from values and references
TODO: This section needs to be updated to reflect the addition of `-> val`
returns in [proposal #5434](/proposals/p5434.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
@@ -645,6 +651,9 @@ specialized constructs given the specialized nature of these operations.
### Reference types
TODO: This section needs to be updated to reflect
[proposal #5434](/proposals/p5434.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
@@ -888,12 +897,12 @@ 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 Self *` -- this forces the use of a [_pointer_](#pointers) to the
original 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 Self *`, then the type fields
will be accessible as [_value expressions_](#value-expressions) using the normal
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`
@@ -904,7 +913,7 @@ 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 Self*`.
- 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`.
+1667
View File
File diff suppressed because it is too large Load Diff