Fix issue 19037: use move/moveEmplace in Nullable#6619
Fix issue 19037: use move/moveEmplace in Nullable#6619dlang-bot merged 4 commits intodlang:masterfrom
Conversation
|
Thanks for your pull request and interest in making D better, @FeepingCreature! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla references
Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + phobos#6619" |
7c64986 to
4dfb7a8
Compare
|
On a first glance the changes look good. Can you add one more test cases for payload types for which the use of |
|
Related question: should call opAssign on the T in the Nullable? Or |
|
Testcase added. I'll just stick with "always move" for now, for internal consistency. |
|
ping |
std/typecons.d
Outdated
| value = A value of type `T` to assign to this `Nullable`. | ||
| */ | ||
| void opAssign()(T value) | ||
| @trusted void opAssign()(T value) |
There was a problem hiding this comment.
How can we be sure that this is always @safe?
Wouldn't it be better to make move/moveEmplace due the inference (and rather fix that than blasting things with @trusted here)?
There was a problem hiding this comment.
Because move is @safe and moveEmplace is only unsafe because it calls memcpy, which we know to be safe because the target variable is only initialized to .init and no methods have been called on it yet, or .destroy has been called on it; semantically it's uninitialized memory.
"A method that can call one of two methods, one of which is @safe and the other of which we know to be @safe but cannot convince the compiler" is an archetypal usecase for @trusted.
edit: The interesting question would be if moveEmplace calls postblit.
edit: It does not. Which makes sense - if there's any spot where postblit should be called, it's when generating the opAssign call. What about struct destructors..?
edit: on the other hand, there's an actual problem in that we move the struct but then we don't call postblit, but then we destruct it in the opAssign function!
95dc4aa to
3f4b65d
Compare
|
The assumptions used in this code are documented in dlang/dlang.org#2410 . Note that DMD does not play ball yet - PR incoming. |
|
Uh, as far as I can tell these aren't my failures. |
|
CircleCi is unrelated -> #6626, but not sure why DAutoTest failed, but as it seems to be offline at the moment, it could be related to this. |
|
Note that the current state of this PR does not make very much sense without dlang/dlang.org#2410 and dlang/dmd#8462 , which formalize the assumption gleamed from |
|
Hm, though given that T.init.~this is supposed to be valid anyways apparently, I guess this should go ahead regardless. ping edit: Rebased to rerun tests. |
3f4b65d to
3557fd6
Compare
|
Re CircleCI, I really don't know what's going on there. And ci.dlang.io just times out. |
|
Don't worry about this. CircleCi is currently broken as someone managed to change the -dip1000 semantics and break std.uni (see #6626). |
|
@n8sh There's a potential problem with this PR. As opposed to before, Nullable now actually depends on the assumption that calling a destructor on T.init is a valid operation. This may make problems if people use structs with invariants that check for null or 0 and an (implicit?) destructor. I hate to delay my own PR, but wanna wait for CircleCI to come back so we can see? Or will it take too long? |
|
@FeepingCreature Merging anything but trivial changes without waiting for CircleCI would be imprudent. How do you mean this PR makes |
|
Previously, in |
|
@FeepingCreature you could avoid resetting void opAssign()(T value)
{
static if (!hasElaborateAssign!T && !hasElaborateDestructor!T)
{
_value = value;
}
else
{
import std.algorithm.mutation : swap;
swap(value, _value);
}
_isNull = false;
}That way if |
|
True, but ... initially Musical chairs with a crash... |
|
The small benefit to The only way I know of to prevent a struct's destructor from being called is by passing it around inside a union. We could use |
|
Unions! That's it! |
dbe35bb to
ee1775a
Compare
|
Explicit test for destructor call on reassignment added. |
|
ee1775a to
bf23790
Compare
|
retrying with hope! |
Fixes issue 19037.
unittest fix
…plicit destructor that only runs if !isNull
bf23790 to
b0972bd
Compare
|
The Mecca failure is unrelated. If Buildkite passes you can safely assume that the failure is spurious. Buildkite runs the same tests as Jenkins and is supposed to replace it once the logs are public. |
|
buildkite has been running for two hours now...? DAutoTest also seems to be stuck. |
|
Nay, DAutoTest is just pretty busy and buildkite doesn't run with full resources yet as its experimental (it only uses two cheap VPS of mine for all build jobs). Feel free to discuss CI problems in #ci on Slack |
|
Happy One Month Anniversary of this PR! A merge tag would be a really good present... edit: Blech. Just checked the docs and found that fields with destructors in unions are technically forbidden. 😢 Do I have to make a DIP/DMD PR to implement edit: Proposed semantics. edit: Correction! The docs are wrong about this (see dlang/dmd#5830 ). So this feature is actually 100% legal. Amazing. (Not the good kind of amazing.) |
|
I think this change is partially incorrect. The reason is that this moves Consider the Nullable opAssign()(T value)To support non-copyable objects, we'd just use More importantly, there is a unit test which verifies that during an Well, this is a problem. D does not have move semantics, so So, the destructor will be called on
The unit test checks that the implementation does 2 and not 1. This is a problem if we want One conclusion we can draw from the above is that, because D lacks true move semantics, it should always be valid to call the destructor on the default value; otherwise, such objects will be incompatible with BTW, without move semantics, I don't see any way to support both non-copyable and init-less types :( |
|
It's been six years, so I don't remember too well. I think I was trying to make Nullable act like Rebindable here? This is before I realized that Rebindable really needed to be its own thing, with RebindableNullable on top. But... Nullable specifically can skip destruction, via the union hack, because it knows for a fact if its value field is populated or not. If it's not doing that, it probably should. edit: Oh wait, you mean the parameter. Yeah. |
OK, but you can't do the same thing for |
|
Yeah I'd forgotten about the parameter. Fwiw, I think this is not the only place that assumes that |
|
Some amendments to the above:
|
The problem is actually the inverse - the code and tests try to avoid calling the destructor on |
|
I think from a language design perspective this is some sort of "pick two out of three" thing between "getting away without a full borrow checker", "field mutation" and "move semantics". Pretty sure D has just design-overloaded itself in this matter. |
|
Well, here's the PR: #8874 |
|
I am not a language designer but move semantics (i.e. invoked explicitly using some kind of new syntax) seem like a purely additive improvement. |
Makes Nullable usable with every type, aside the crazies who have pointers to their members. opAssign of the underlying type will no longer be called.
This enables Nullable on, for instance, structs with
@disable this();and aSysTime.