Thread overview
D structs weak identity and RAII
Jun 19, 2017
Boris-Barboris
Jun 19, 2017
Ali Çehreli
Jun 19, 2017
Boris-Barboris
June 19, 2017
Hello, I was trying to write some templated unique pointers.
Idea was simple: struct UniquePointer that handles underlying pointer in RAII-style and WeakPointer struct that is spawned by UniquePointer. Weak pointer is handled differently in my collections, wich subscribe to the event of UniquePointer destruction, so that there are no dangling references left all over the heap (I use Mallocator). Collection's insertFront\Back methods register callbacks in Weak pointer itself, and all existing weak pointers are registered in Unique pointer.

That, unfortunately, failed at the very beginning. Then I wrote a unit test to investigate:

https://dpaste.dzfl.pl/d77c72198095

1). line 47 and 76 together mean, that there is basically no reliable way to write uniqueptr method wich returns WeakPointer, registered by the correct pointer in it's constructor. Or is there? Is usage of &this in constructor (or in struct methods in general) fundamentally unreliable in D?
2). postblit was never called when returning struct from function. I noticed it was called once on line 91, if i removed opAssign with ref argument. What are the rules? Why is it always called when passing struct to function and creating local copy of the parameter (lines 113, 127, 139), and why is opAssign not called?
3). Is there a way to reliably encapsulate all assignments? Looks like complete mess will happen should I apply some std.algorithm functions on array of structs with overloaded operators.
4). Any suggested workarounds? I never tried it in C++, but, IIRC, struct life cycle is much more consistent there ang gives the strict control I desire.
5). If all this is a design choice, what is the reason behind it?
June 18, 2017
On 06/18/2017 06:22 PM, Boris-Barboris wrote:

> https://dpaste.dzfl.pl/d77c72198095
>
> 1). line 47 and 76 together mean, that there is basically no reliable
> way to write uniqueptr method wich returns WeakPointer, registered by
> the correct pointer in it's constructor. Or is there? Is usage of &this
> in constructor (or in struct methods in general) fundamentally
> unreliable in D?

It's unreliable because structs are value types in D, which means that they can be moved around freely. This is why self-referencing structs are illegal in D.

> 2). postblit was never called when returning struct from function.

Rvalues are automatically moved and there is also RVO.

> 3). Is there a way to reliably encapsulate all assignments?

I think so.

> 4). Any suggested workarounds? I never tried it in C++, but, IIRC,
> struct life cycle is much more consistent there ang gives the strict
> control I desire.

Yes, C++ has a very well defined object lifecycle.

> 5). If all this is a design choice, what is the reason behind it?

Some are design choices and some are bugs. For example, a fundamental bug has just been fixed: When a constructor threw, the destructors of already-constructed members were not being called. I think the fix is in git head but not released yet.

I can't claim expertise but here is a quick and dirty proof of concept that Vittorio Romeo and I had played with a few weeks ago:

import core.stdc.stdio;
import core.stdc.stdlib;
import std.algorithm;
import std.stdio;

shared byte b; // to prevent compiler optimizations

struct UniquePtr {
    void * p;

    this(void * p) {
        this.p = p;
        import core.atomic;
        core.atomic.atomicOp!"+="(b, 1);
    }

    ~this() {
        free(p);
    }

    @disable this(this);

    UniquePtr move() {
        void * old = p;
        p = null;
        return UniquePtr(old);
    }
}

void consumer(UniquePtr u) {
}

UniquePtr producer(int i) {
    auto u = UniquePtr(malloc(42));
    return i ? UniquePtr(malloc(56)) : u.move();
}

void main() {
    consumer(UniquePtr(malloc(2)));

    auto u = UniquePtr(malloc(5));
    consumer(u.move());

    auto u2 = producer(43);
}

Note how post-blit is disabled there. In addition to a moveFrom(), that's exactly what Atila Neves had to do in this automem library as well:

  https://github.com/atilaneves/automem

Ali

June 19, 2017
On Monday, 19 June 2017 at 06:34:49 UTC, Ali Çehreli wrote:
> It's unreliable because structs are value types in D, which means that they can be moved around freely. This is why self-referencing structs are illegal in D.

I guess it's more like the spec states, that they can be moved vithout notice. Value type semantics themselves do not mean spontaneous uncontrolled mobility.

> I can't claim expertise but here is a quick and dirty proof of concept that Vittorio Romeo and I had played with a few weeks ago:
>
> ...
>
> shared byte b; // to prevent compiler optimizations
>
> struct UniquePtr {
>     void * p;
>
>     this(void * p) {
>         this.p = p;
>         import core.atomic;
>         core.atomic.atomicOp!"+="(b, 1);
>     }

Very interesting hack, thank you.

> Note how post-blit is disabled there. In addition to a moveFrom(), that's exactly what Atila Neves had to do in this automem library as well:
>
>   https://github.com/atilaneves/automem

Nice, thanks.