On Wednesday, 17 February 2016 at 22:20:00 UTC, Matt Elkins wrote:
So in a different thread someone mentioned that when arrays are grown an implicit copy could be called on all the elements, as they might need to be copied over to a new, larger block of memory. This makes sense, and is expected. However, it got me concerned: what if the post-blit was disabled because it is illegal to copy the object? I am using a lot of objects exactly like this, and wanted to make sure my program wasn't working by coincidence since my dynamic arrays are still small for now. So I tested:

[code]
import std.stdio;
@safe:

bool scopeEnded;

struct Foo
{
    @disable this(this);

this(int val) {writeln("Constructing: ", val, " (", &this, ")"); value = val;} ~this() {writeln("Destroying: ", value, " (", &this, ")"); assert(value == int.init || scopeEnded);}
    int value;
}

unittest
{
    Foo[] foos;
    for (auto i = 0; i < 10000; ++i)
    {
        ++foos.length;
        foos[$ - 1] = Foo(i);
    }

    writeln("Scope about to end");
    scopeEnded = true;
}
[/code]

[output]
Constructing: 0 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 1 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 2 (18FDA8)
Destroying: 0 (18FD6C)

....<snipped>....

Constructing: 410 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 411 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 412 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 413 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 414 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 415 (18FDA8)

Destroying: 0 (18FD6C)
core.exception.InvalidMemoryOperationError@src\core\exception.d(679): Invalid 
memory operation
Constructing: 416 (18FDA8)
----------------
Destroying: 0 (18FD6C)

Constructing: 417 (18FDA8)
core.exception.InvalidMemoryOperationError@src\core\exception.d(679): Invalid 
memory operation
Destroying: 0 (18FD6C)
Constructing: 418 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 419 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 420 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 421 (18FDA8)
----------------
Destroying: 0 (18FD6C)
Constructing: 422 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 423 (18FDA8)

....<snipped>....

Constructing: 506 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 507 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 508 (18FDA8)
Destroying: 0 (18FD6C)
Constructing: 509 (18FDA8)
Destroying: 0 (18FD6C)
Destroying: 29 (5201F4)
Program exited with code 1
[/output]

Note that the invalid memory operation lines change relative order in this output, I think maybe it is stderr instead of stdout.

So now I'm wondering:
* Is the fact that this compiles a bug? If copying can happen under the hood, shouldn't the @disable this(this) prevent Foo being used this way? Or should copying be happening at all (the compiler could instead choose to "move" the Foo by blitting it and NOT running the destructor...though I don't know whether that is a safe choice in the general case)?

From looking at the addresses of the objects it seems like they're being constructed on the stack (as a temporary object), copied (blitted) to the array on the heap and then the temporary object has it's destructor being run. I think this is safe because you can't access the temporary (only the compiler can see it), so it's uniqueness is preserved. Unnecessarily running destructor is either an optimization opportunity or a manifestation of the bug that you reported here: https://issues.dlang.org/show_bug.cgi?id=15661. I suggest testing this code again with a newer compiler (see my answer in the other thread - http://forum.dlang.org/post/omfyqfulgyzbrxlzr...@forum.dlang.org).

* What is the invalid memory operation? It doesn't occur if I remove the assert or make the assert never fail, so I'm guessing it has to do with failing an assert in the destructor? This points bothers me less than the previous one because I don't expect an assert-failing program to behave nicely anyway.

The "Invalid memory operation" error is thrown only by the GC (AFAIK) when the user tries something unsupported like allocating or freeing in a destructor, while a GC collection is being run. I think that it's not possible to trigger it in @nogc code. From a short debug session, I managed to find out that it crashes on the `++foos.length` line, somewhere inside this function: https://github.com/D-Programming-Language/druntime/blob/e47a00bff935c3f079bb567a6ec97663ba384487/src/rt/lifetime.d#L1265. Unfortunately I currently don't have enough time to investigate further.
Can you please file a bugzilla issue with this test case?

Reply via email to