`@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