April 05, 2018
TL;DR

The opMove DIP proposal (PR 109) is assuming a world where D first copies data and then calls user functions to correct it (if applicable). With the suggested this(this) deprecation, this is no longer the case.

Links

DIP PR: https://github.com/dlang/DIPs/pull/109

this(this) deprecation announcement: https://forum.dlang.org/thread/p9p64v$ue6$1@digitalmars.com


Introduction

About three weeks ago I submitted a proposed DIP to add an optional "opMove" to structs. This operator would be called after the compiler (or standard library) do an implicit move of a struct, to allow it to update both internal and external references.

Andrei's announcement, from 4 days ago, about wishing to deprecate this(this), changes that. Personally, I feel the problems are not so much actual weakness in the proposed DIP, and more to do with language consistency.

The Problems Exposed by this(this)

The main problems relevant to this(this) are:

1. Const/immutable/shared objects being copied result in rather twisted logic of what may or may not be done.
2. If struct A @disable this(this), then any struct that has A as a member becomes uncopyable. Unlike C++, this is not overrideable by anything the containing struct might do.

I feel that problem #1 is, actually, not as serious for move as it is for copy. Unlike copy, for move the object being copied has no compiler maintained external references. We can relax the rules quite a bit during a point in time where noone is referencing data (otherwise, we wouldn't be able to initialize immutable objects).

Problem #2 is still relevant, and continues to be relevant for the move case.

With that in mind, the main problem I find with my proposed DIP is that, if we switch from post blit to copy overriding, we should equally switch from post-move to move overriding. The opMove suggestion relies on a certain D idiosyncrasy that we are now trying to deprecate.

This, I believe, is the main issue facing us. I believe my DIP proposes an adequate solution to problem #1, and I believe we can work around problem #2 (or just live with it) without straying too much from the current layout.

One Proposed Solution

One way to fix this is to change the DIP according to the following guidelines:

1. Make opMove solely responsible for moving the data (move constructor like).
2. If neither the struct nor any of its members define a custom opMove, the struct may be copied using memcpy (and that's what the implicit opMove does).
3. If the struct does not define a custom opMove, but one of its members does, then the compiler defines an implicit opMove that memcpys what can be memcpy, and calls opMove on what can't.

There are several disadvantages for this road map compared to the current proposal:

1. A new implicit member is added to structs. This is poses a much greater backward compatibility change compared to the current proposal, as it affects introspection constructs such as __traits(allMembers).

2. This new member has non-trivial implementation. This is a potential problem in case you want to override it but keep much of the original implementation.

My Proposed Solution

My proposed solution is this:

1. Keep the current DIP as is with one change: rename opMove to opPostMove.

2. Since any solution to this(this) deprecation is likely going to include fairly major backward compatibility breakage, include the transition to opMove, as defined above, as part of the inevitable DIP that will resolve this(this). This way we'll be facing one concentrated painful transition instead of two.

I am interesting in hearing the community's thoughts on this.

Thank you,
Shachar