`@rvalue ref` and `@universal ref`

Quirin Schroll qs.il.paperinik at gmail.com
Tue Aug 6 15:26:49 UTC 2024


On Monday, 29 July 2024 at 05:52:40 UTC, IchorDev wrote:
> On Wednesday, 3 July 2024 at 11:50:19 UTC, Quirin Schroll wrote:
>> Add two function parameter attributes `@rvalue` and 
>> `@universal` which must be used together with `ref` (similar 
>> as `auto` is only valid as `auto ref`).
>
> This would be really useful for sure, as long as escaping 
> rvalue references isn’t possible, so I guess it’d have to work 
> like scope?

An `@rvalue ref` parameter definitely could be `return`. Like 
every `ref`, you can’t actually escape it as it might local, in 
fact, even an rvalue:

```d
// Use -dip1000

struct MaterializeL(T) { T* _value; @property ref T value() => 
*_value; }
struct MaterializeR(T) { T value; }

MaterializeL!T materializeU(T)(return ref T value)
{
     return MaterializeL!T(&value);
}
MaterializeR!T materializeU(T)(T value)
{
     import core.lifetime : move;
     return MaterializeR!T(move(value));
}

@disable MaterializeL!T materializeR(T)(ref T value) @nogc 
nothrow pure @safe
{
     return MaterializeL!T(&value);
}
MaterializeR!T materializeR(T)(T value)
{
     import core.lifetime : move;
     return MaterializeR!T(move(value));
}

ref int f(return ref int x) @safe { x = 10; return x; }
ref int g(return ref int x) @safe => x += 1;

void main() @safe
{
     int x = (0).materializeU.value.f.g;
     assert(x == 11);
     // int* p = &(0).materializeU.value.f.g; // Error, escapes 
reference to temporary

     // On lvalues, materializeU.value is a no-op:
     int* p = &x.materializeU.value.f.g;

     int y = (0).materializeR.value.f.g;
     // int* q = &y.materializeR.value.f.g; // Error: materializeR 
disabled for lvalues
}
```

> Also I’m not sure about the at-attribute-based syntax. It makes 
> the feature feel like an afterthought, even though this 
> functionality is very useful. Perhaps a better solution 
> syntax-wise could be to use `in` as the ‘rvalue’ attribute, and 
> `auto in` could be the ‘universal’ attribute. So then:
> - `@rvalue` —> `in`
> - `@rvalue ref` —> `in ref`
> - `@universal ref` —> `auto in ref`
> - `@universal auto ref` —> `auto in auto ref`

I have a DIP draft on my old computer which proposed something 
very similar to that: `in` and `ref` both mean *bind by 
reference*, in particular, `in` would have meant *allow rvalues* 
and `ref` *allow lvalues* (so both means allow both, none means 
bind-by-value). The idea of making `in` mean rvalues and `ref` 
mean lvalues isn’t new. The issue is, it can’t be done anymore. 
For all the time it was there, `in` meant `const`. Also, and this 
is the key killer: Conceptually, `in` means input parameter, i.e. 
something that supplies your function with data to make decisions 
on, call it configuration if you like, and this is what informed 
the `-preview=in` semantics.

Another aspect I don’t like stylistically, is `auto in`. As a 
parameter storage class, `auto` means infer and requires 
templates, plus one could introspect what the inference 
determined. Although it doesn’t work like that, `auto ref` for 
variables could be used in non-template contexts, where `ref` is 
inferred from the initializer.
Contrary to that, `@universal` requires materializing rvalues. 
Essentially, binding `arg` to a `@universal ref` parameter would 
be exactly like passing `arg.materializeU.value` to a `ref` 
parameter, and likewise passing `arg.materializeR.value` for 
binding to an `@rvalue ref` parameter.

When writing DIPs, I take the future state of the language into 
account, that includes the new `in` semantics. It makes no sense 
proposing something that would be incompatible with something 
that’s going to be in the language; e.g. in my Primary Type 
Syntax DIP, I took care it won’t conflict (conceptually) with 
`ref` variables, and lo and behold, `ref` variables are here to 
stay. `in` is going to mean `scope const @universal ref` with the 
caveat that for small trivially copyable types, ignore the 
`@universal ref`. Maybe that could also go into the DIP: 
`@optimized (@rvalue/@universal) ref` which is defined to be a 
copy for small trivially copyable types. That would render `in` 
an abbreviation for `scope const @optimized @universal ref`. It’s 
been the only thing I disliked about the new `in` semantics. It 
can do things in a bundle that aren’t available as single items. 
Same with non-static struct member functions binding the instance 
by reference, no matter if it’s called on an lvalue or rvalue. It 
makes little sense that this exclusive to the implicit `this` 
parameter.


> This way it’s both shorter to write[.]

That I consider the wordiness a win. Not only is it clearer and a 
pure addition, those parameter attributes are an advanced tool.

> […] [A]nd we can **finally** make `in` do something useful by 
> default.

But `-preview=in` already does that.


More information about the dip.ideas mailing list