`in` parameters made useful

Mathias LANG geod24 at gmail.com
Fri Jul 31 21:49:25 UTC 2020


Hi everyone,
For a long time I've been pretty annoyed by the state of `in` 
parameters.
In case it needs any clarification, I'm talking at what's between 
the asterisks (*) here: `void foo (*in* char[] arg)`).

While they always seemed like a good idea, they never really 
added anything: `in` was supposed to be `const scope`, then, when 
the time came to make `scope` actually do something (read: 
DIP1000), `scope` was removed from `in`!

This was re-added last release (DMD 2.092) where the 
`-preview=in` switch was added 
(https://dlang.org/changelog/2.092.0.html#preview-in). So now, if 
you want `in` to mean what it's documented to be, you need to 
throw in both `-preview=dip1000` and `-preview=in`.

But then... That still feels incomplete. I deal with a lot of C++ 
interop code, and we can't use `in` without using `ref`, because 
otherwise we trigger copy constructors / destructors of 
aggregates we have no control over. We also have some value types 
which can get pretty big, so we don't want to pass those by 
value, either. So easy solution, add `ref` ? But then, we cannot 
pass rvalues. A real-world example of this is 
`doSomething(myData.getHash())` where `getHash` return a 
`ubyte[64]`.

Luckily we have a `-preview=rvaluerefparam` switch, which should 
do what I want, right ? Well, as I said multiple times on this 
forum, it's so utterly broken it's not even funny:
- https://issues.dlang.org/show_bug.cgi?id=20704
- https://issues.dlang.org/show_bug.cgi?id=20705 (I'm sorry, 
WHAT?)
- https://issues.dlang.org/show_bug.cgi?id=20706

Because of 20705, that switch is completely unusable for any real 
world application.
There are alternatives to this (which we are using), such as 
using `auto ref`. But it requires to use templates, which we 
cannot do with delegates, or virtual methods.

Now I don't really like to rant without having a solution to 
offer. And it turns out, that's the whole motivation for this 
post. I have a PR that solves *all* those problems at once. All 
it needs is a bit of attention / review / feedback!

The PR in question is here: 
https://github.com/dlang/dmd/pull/11000

What does it do ?
A.0) It fixes `in` to be an actual storage class, not something 
that is lowered almost immediately.
    This was necessary for the implementation to work, but has two 
nice side effects:
      1) it fixes error messages (currently `void foo(in int)` 
will display as `void foo(const(int))` in error messages);
      2) it fixes header generation (`.di` files) so that `in` is 
kept instead of seeing `const` or `scope const`, depending on 
`-preview=in`;
    I think this change has value in itself, so I submitted it as 
a separate PR (https://github.com/dlang/dmd/pull/11474), which 
itself needs a tiny adjustment in Phobos 
(https://github.com/dlang/phobos/pull/7570).

A.1) It gives a mangling to `in`: This is necessary to avoid some 
ambiguity. The main two user-visible side effects will be that 
older debuggers won't be able to demangle `in`, and that, once we 
update druntime, stack traces will show the correct signature for 
functions using `in` (currently they suffer from the same bug as 
the error message / header generation). This is also part of the 
aforementioned PR.

B) It makes `in` take the effect of `ref` when it makes sense. It 
always pass something by `ref` if the type has elaborate 
construction / destruction (postblit, copy constructor, 
destructors). If the type doesn't have any of those it is only 
passed by `ref` if it cannot be passed in register. Some types 
(dynamic arrays, probably AA in the future) are not affected to 
allow for covariance (more on that later). The heuristics there 
still need some small improvements, e.g. w.r.t. floating points 
(currently the heuristic is based on size, and not asking the 
backend) and small struct slicing, but that should not affect 
correctness.

C) It implements covariance rules: if you have a `void 
toString(scope void delegate(in char[]) sink)` method, you can 
pass it `void writeToScreen(const scope char[])`. If you have 
`void output(scope void delegate(in ubyte[64]))` you can pass it 
`void saveHash(const scope ref ubyte[64])`. Simple stuff.

D) It allows to pass rvalues to `in`. Because we know it's 
`scope`, so it cannot be escaped (allegedly), and it's `const`, 
so it cannot be modified, it's only logical that you can give it 
rvalues.

Interestingly, @benjones pointed out in the PR that this is 
similar to one of Herb Sutter's proposal for C++: 
https://youtu.be/qx22oxlQmKc?t=1258

I hope this will generate interest with people hitting the same 
problem. I tried this with my project (which depends on ~10 
libraries including Vibe.d and does C++ interop) and things just 
worked when changing `scope const auto ref` to `in`, and clearing 
up a few places where `in` parameters were escaped, or there was 
both an `in ref` and an `in` overload.

Last, but not least, if this gets accepted it would pave the way 
for another awesome change, having `checkaction=context` the 
default for D.
If you look at 
https://github.com/dlang/druntime/blob/104ac712331e4d3573fc277084334a528b5dadb1/src/core/internal/dassert.d you'll find that sweet `auto ref const scope` everywhere.


More information about the Digitalmars-d mailing list