Thread overview
Safely moving structs in D
Jan 23, 2017
bitwise
Jan 23, 2017
bitwise
Jan 23, 2017
Ali Çehreli
Jan 24, 2017
bitwise
Jan 23, 2017
sarn
Jan 24, 2017
Jonathan M Davis
Jan 25, 2017
bitwise
January 23, 2017
Is it ok to memcpy/memmove a struct in D?

Quote from here:
https://dlang.org/spec/garbage.html

"Do not have pointers in a struct instance that point back to the same instance. The trouble with this is if the instance gets moved in memory, the pointer will point back to where it came from, with likely disastrous results."

This seems to suggests it's ok to move structs around in memory without calling their postblit...but if this is the case, why does postblit even exist, if it's not strictly guaranteed to be called after the struct has been blitted?


January 23, 2017
I'm confused about what the rules would be here.

It would make sense to call the postblit if present, but std.Array currently does not:
https://github.com/dlang/phobos/blob/04cca5c85ddf2be25381fc63c3e941498b17541b/std/container/array.d#L884
January 23, 2017
On Monday, 23 January 2017 at 22:26:58 UTC, bitwise wrote:
> Is it ok to memcpy/memmove a struct in D?
>
> Quote from here:
> https://dlang.org/spec/garbage.html
>
> "Do not have pointers in a struct instance that point back to the same instance. The trouble with this is if the instance gets moved in memory, the pointer will point back to where it came from, with likely disastrous results."
>
> This seems to suggests it's ok to move structs around in memory without calling their postblit...but if this is the case, why does postblit even exist, if it's not strictly guaranteed to be called after the struct has been blitted?

You may need the postblit for a *copying* blit.  For example, if a struct does reference counting, the postblit will need to increment the count for the new copy.  It's a slightly different solution to what C++ solves with copy constructors, assignment operators, etc.  Compared to C++, the D approach is a bit simpler and trades off a little flexibility for more opportunities for the compiler to generate efficient code.

Here's the situation in C++, BTW:
http://en.cppreference.com/w/cpp/language/rule_of_three
January 23, 2017
On 01/23/2017 02:58 PM, bitwise wrote:
> I'm confused about what the rules would be here.
>
> It would make sense to call the postblit if present, but std.Array
> currently does not:
> https://github.com/dlang/phobos/blob/04cca5c85ddf2be25381fc63c3e941498b17541b/std/container/array.d#L884
>

Post-blit is for copying though. Moving should not call post-blit. You may want to look at the implementation of std.algorithm.move to see how it plays with post-blit:

  https://dlang.org/phobos/std_algorithm_mutation.html#.move

Ali

January 24, 2017
On Monday, 23 January 2017 at 23:04:45 UTC, Ali Çehreli wrote:
> On 01/23/2017 02:58 PM, bitwise wrote:
>> I'm confused about what the rules would be here.
>>
>> It would make sense to call the postblit if present, but std.Array
>> currently does not:
>> https://github.com/dlang/phobos/blob/04cca5c85ddf2be25381fc63c3e941498b17541b/std/container/array.d#L884
>>
>
> Post-blit is for copying though. Moving should not call post-blit. You may want to look at the implementation of std.algorithm.move to see how it plays with post-blit:
>
>   https://dlang.org/phobos/std_algorithm_mutation.html#.move
>
> Ali

That's a good point.

It didn't click at first, but checking for postblit is done with 'hasElaborateCopyConstructor(T)'.

I had thought that what memmove was doing would be considered "blitting", and hence require a postblit afterwards.

I did look at std.move, but was mistaken about which code path was being taken.
It seemed like structs that defined only a postblit would have been moved by assignment:

https://github.com/dlang/phobos/blob/366f6e4e66abe96bca9fd69d03042e08f787d040/std/algorithm/mutation.d#L1310

But in actuality, the memcpy branch fires because hasElaborateAssign(T) returns true for structs with a postblit - which was unexpected. I don't really understand why, but this makes things clearer.

  Thanks

January 24, 2017
On Monday, January 23, 2017 22:26:58 bitwise via Digitalmars-d-learn wrote:
> Is it ok to memcpy/memmove a struct in D?
>
> Quote from here:
> https://dlang.org/spec/garbage.html
>
> "Do not have pointers in a struct instance that point back to the same instance. The trouble with this is if the instance gets moved in memory, the pointer will point back to where it came from, with likely disastrous results."
>
> This seems to suggests it's ok to move structs around in memory without calling their postblit...but if this is the case, why does postblit even exist, if it's not strictly guaranteed to be called after the struct has been blitted?

Moving structs is fine. The postblit constructor is for when they're copied. A copy is unnecessary if the original isn't around anymore - e.g. passing an rvalue to a function can move the value; it doesn't need to copy it. Even passing an lvalue doesn't need to result in a copy if the lvalue is not referenced at any point after that function call. However, if you're going to end up with two distinct copies, then they need to actually be copies, and a postblit constructor will be called.

Types that would need postblit constructors would include anything doing reference counting as well as anything that needs to do a deep copy of something on the heap (though such structs aren't a great idea, since usually copying is assumed to be cheap, and having the postblit constructor allocate on the heap isn't exactly cheap). In reality, I don't think that many structs typically have postblit constructors, but there are definitely use cases where they're needed.

The bit about structs not pointing to themselves is to make it legal to move structs in cases where the compiler knows that only one copy is required, whereas in C++, because pointing to yourself is perfectly legal, the compiler has to do a lot more copying, and they had to introduce move constructors to get around the problem.

- Jonathan M Davis

January 25, 2017
On Tuesday, 24 January 2017 at 11:46:47 UTC, Jonathan M Davis wrote:
> On Monday, January 23, 2017 22:26:58 bitwise via Digitalmars-d-learn wrote:
>> [...]
>
> Moving structs is fine. The postblit constructor is for when they're copied. A copy is unnecessary if the original isn't around anymore - e.g. passing an rvalue to a function can move the value; it doesn't need to copy it. Even passing an lvalue doesn't need to result in a copy if the lvalue is not referenced at any point after that function call. However, if you're going to end up with two distinct copies, then they need to actually be copies, and a postblit constructor will be called.
>
> [...]

Awesome, thanks - this makes sense.