`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