www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 24840] New: Implicit construction with associative array

https://issues.dlang.org/show_bug.cgi?id=24840

          Issue ID: 24840
           Summary: Implicit construction with associative array literals
                    can result in more destructor calls than constructor
                    calls
           Product: D
           Version: D2
          Hardware: All
                OS: All
            Status: NEW
          Severity: normal
          Priority: P1
         Component: dmd
          Assignee: nobody puremagic.com
          Reporter: issues.dlang jmdavisProg.com

I feel like there has to be a more reduced example of this, but I've failed
thus far. In any case, this code (which has the extra strings so that it can
print out what's happening even when the GC calls the destructor):

---
void main()
{
    import std.format;
    import std.stdio;
    import std.variant;

    static struct S
    {
        Variant v;
        string copyMsg;
        string destroyMsg;

        this(T)(T t)
            if(!is(immutable T == immutable S))
        {
            v = t;
            copyMsg = format("copy: %s", v);
            destroyMsg = format("destroy: %s", v);
        }

        this(this)
        {
            writeln(copyMsg);
        }

        ~this()
        {
            writeln(destroyMsg);
        }

        string toString()
        {
            return format("S(%s)", v);
        }
    }

    writeln(__LINE__);

    S[] expected = [
        ["x": S(1), "y": S(2)],
        ["x": S(3), "y": S(4)],
        ["x": S(5), "y": S(6)],
    ];

    writeln(__LINE__);
}
---

prints

---
46
destroy: ["y":S(6), "x":S(5)]
destroy: ["y":S(4), "x":S(3)]
destroy: ["y":S(2), "x":S(1)]
54
destroy: ["y":S(6), "x":S(5)]
destroy: ["y":S(4), "x":S(3)]
destroy: ["y":S(2), "x":S(1)]
destroy: 1
destroy: 2
destroy: 3
destroy: 4
destroy: 5
destroy: 6
---

So, 

---
    S[] expected = [
        ["x": S(1), "y": S(2)],
        ["x": S(3), "y": S(4)],
        ["x": S(5), "y": S(6)],
    ];
---

somehow ends up destroying the implicitly constructed S's in the array - and
then they get destroyed again when the program exits. If there were a copy made
in between, then it could be the copies getting destroyed, but there are no
calls to the postblit constructor. So, either copies are being made without
calling the postblit constructor, or the destructor is simply managing to be
called multiple times somehow.

However, if we change that piece of the code to

---
    S[] expected = [
        S(["x": S(1), "y": S(2)]),
        S(["x": S(3), "y": S(4)]),
        S(["x": S(5), "y": S(6)]),
    ];
---

then we get

---
46
54
destroy: ["y":S(6), "x":S(5)]
destroy: ["y":S(4), "x":S(3)]
destroy: ["y":S(2), "x":S(1)]
destroy: 1
destroy: 2
destroy: 3
destroy: 4
destroy: 5
destroy: 6
---

This would be correct behavior. The number of copies and destructions match. It
also indicates that no copies or destructions occurred when constructing the
array, which is what I would have expected, but the important thing is that we
have the same number of copies of the objects and destructions.

However, the big thing to note here is that this means that using the implicit
construction syntax results in different behavior from using the explicit
construction syntax when the two are supposed to be semantically identical. So,
it would appear that something is going wrong when the compiler generates the
code for the implicit construction.

I have yet to be able to reproduce this without a variant type (though I
originally ran into it with a variant type at work rather than
std.variant.Variant, so it's not specific to std.variant.Variant). So, I
suspect that it has something to do with how S's are constructed inside the AA
literals and then used to implicitly construct S's from those AA literals, but
i don't know. I also haven't been able to reproduce it using just array
literals.

That being said, I can say that if I change the constructor to

---
        this(int i)
        {
            v = i;
            copyMsg = format("copy: %s", v);
            destroyMsg = format("destroy: %s", v);
        }

        this(S[string] aa)
        {
            v = aa;
            copyMsg = format("copy: %s", v);
            destroyMsg = format("destroy: %s", v);
        }
---

it doesn't change the behavior, so the fact that S's constructor is templated
doesn't seem to be affecting things (though maybe Variant's constructor is
somehow).

All in all, this is just weird, but it does mean that we have a subtle footgun
for folks who decide to use the implicit construction syntax with types that
have a destructor.

--
Oct 30