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 refparameter only binds rvalues, which are caller-side materialized. - A
@universal refparameter binds both lvalues and rvalues. - As before: Plain
refonly 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).
struct BigStruct;
int getN(BigStruct big) => big.n;
// ❌ copies lvalues
// ❌ result is not a reference
ref inout(int) getN(ref inout(BigStruct) big) => big.n;
// ❌ no rvalue arguments allowed
int getN(in BigStruct big) => big.n;
// ❌ can’t return by ref: big is scope
// ❌ if it could, result would be const
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
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.
Permalink
Reply