`@rvalue ref` and `@universal ref`
Quirin Schroll
qs.il.paperinik at gmail.com
Wed Jul 3 11:50:19 UTC 2024
Add two function parameter attributes `@rvalue` and `@universal`
which must be used together with `ref` (similar as `auto` is only
valid as `auto ref`).
* An `@rvalue ref` parameter only binds rvalues, which are
caller-side materialized.
* A `@universal ref` parameter binds both lvalues and rvalues.
* As before: Plain `ref` only binds lvalues.
For function returns, there will be no `@universal ref`, but
`@rvalue ref`. This is, however, equivalent to `ref` inside the
function, meaning it must return an lvalue. The only difference
between `ref` and `@rvalue ref` returning functions is
caller-side. The result of a `@rvalue ref` returning function is
considered an rvalue: A move instead of a copy is issued, it
cannot have its address taken, etc.
For `extern(C++)` functions, `@rvalue ref T` mangles like C++’s
`T&&`; `@universal ref` is not allowed for `extern(C++)`
functions.
---
**Rationale:** `ref` can be used to make mutations transparent to
the caller. There, only lvalue arguments make sense. But `ref`
can also be used to alleviate copies. While there is `in` (with
`-preview=in`), that also makes the parameter `const`, and some
types simply don’t work well with `const`. A `@universal ref`
binds arguments with whatever qualifier specified, including none
(i.e. mutable).
```d
struct BigStruct;
int getN(BigStruct big) => big.n;
// ❌ copies lvalues
// ❌ result is not a reference
```
```d
ref inout(int) getN(ref inout(BigStruct) big) => big.n;
// ❌ no rvalue arguments allowed
```
```d
int getN(in BigStruct big) => big.n;
// ❌ can’t return by ref: big is scope
// ❌ if it could, result would be const
```
```d
int getN(const(BigStruct) big) => big.n; // for rvalues
ref inout(int) getN(ref inout(BigStruct) big) => big.n; // for
lvalues
// ❌ can’t simply take address anymore
// ❌ for rvalues: result is not an lvalue
```
```d
ref inout(int) getN(@universal ref inout(BigStruct) big) => big.n;
// ✔️ no copies
// ✔️ returns by ref
// ✔️ allows rvalue arguments
// ✔️ single function, `auto fp = &getN` works
// ✔️ for rvalue argument: return value has lifetime to the end of
the statement
// ✔️ for mutable argument: result is mutable
```
As far as conversions are concerned, an `R function(@universal
ref T)` implicitly converts to `R function(ref T)` and `R
function(@rvalue ref T)`. The reasoning being that a `@universal
ref` parameter behaves exactly like a `ref` parameter for lvalue
arguments, so the conversion merely “forgets” that rvalue
arguments would be expected and allowed. The conversion to
`@rvalue ref` similarly forgets that lvalue arguments were
allowed.
`ref` and `@rvalue ref` returns are conversion-incompatible for
implicit conversions. As they are not binary-incompatible, an
explicit cast can be used, which is a `@system` operation.
More information about the dip.ideas
mailing list