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?