Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Improved semantics for destroy() #2126

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
159 changes: 122 additions & 37 deletions src/object.d
Original file line number Diff line number Diff line change
Expand Up @@ -2623,9 +2623,7 @@ unittest
private void _destructRecurse(S)(ref S s)
if (is(S == struct))
{
static if (__traits(hasMember, S, "__xdtor") &&
// Bugzilla 14746: Check that it's the exact member of S.
__traits(isSame, S, __traits(parent, s.__xdtor)))
static if (__traits(hasMember, S, "__xdtor"))
s.__xdtor();
}

Expand All @@ -2644,9 +2642,7 @@ private void _destructRecurse(E, size_t n)(ref E[n] arr)
void _postblitRecurse(S)(ref S s)
if (is(S == struct))
{
static if (__traits(hasMember, S, "__xpostblit") &&
// Bugzilla 14746: Check that it's the exact member of S.
__traits(isSame, S, __traits(parent, s.__xpostblit)))
static if (__traits(hasMember, S, "__xpostblit"))
s.__xpostblit();
}

Expand Down Expand Up @@ -2922,21 +2918,53 @@ unittest
assert(postblitRecurseOrder == order);
}

// destroy
/********
Destroys the given object and sets it back to its initial state. It's used to
_destroy an object, calling its destructor or finalizer so it no longer
references any other objects. It does $(I not) initiate a GC cycle or free
any GC memory.
Destroys an object by means of calling its destructor, if present. For
types `T` that define a destructor, `destroy` then also fills the
Copy link
Contributor

@JinShil JinShil Mar 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably best to use "... that have a destructor" here since that's the verbage used in the following "NOTE".

object with `T.init` to ensure the object's destructor can be called
again (e.g. when the object goes out of scope). Unlike $(LREF
__delete), `destroy` does not release memory or other resources by
itself (though it is possible that destructors it calls do so).

Note: A `struct` type may have a destructor even if it does not
directly define `~this()`, if at least one field has a destructor
(transitively). A `class` type may have a destructor even if it does
not directly define `~this()`, if at least one field has a destructor
(transitively), or if the destructor is inherited.

Detailed behavior of `destroy(obj)` depends on the type `T` as
follows:

$(UL $(LI If `T` is a primitive type, a `struct` types that does not
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`struct` type (singular)

have a destructor, or a `class` type that does not have a destructor,
the call to has no effect.)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"call to has no effect" Maybe missing "destroy" in there?

Note, this is technically incorrect for a class type with no destructor, as destroying a class instance that has no destructor is still going to call rt_finalize, which at least sets the vtable to null.


$(LI For `class` and `interface` types, the call `destroy(obj)` has no
effect if `obj` is `null`.)

$(LI For `struct` and `class` types that have a destructor (either
defined explicitly or synthesized by the compiler), the destructor is
called. Then the object is initialized using
`T.init`. Reinitialization occurs even if the type disables default
construction with `@disable this()`.)

$(LI For `interface` types, the dynamic `class` object is fetched from
the interface object, and then `destroy` is applied to it.)

$(LI For static and dynamic arrays, `destroy` is applied right-to-left
to all elements. This means multidimensional arrays are destroyed in
depth, transitively (behavior different from $(LREF __delete), which
is by necessity shallow). If one or more destructors throw an
exception, the calls are completed and then the first exception
encountered is rethrown.))
*/
void destroy(T)(T obj) if (is(T == class))
void destroy(T)(T obj) if (is(T == class) || (is(T == interface)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a pre-existing bug in here, as I think the static test will pass for extern(C++) classes, which is definitely NOT correct to call rt_finalize on. Something to think about as we move towards betterC.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And Objective-C classes. There’s a recent change where __traits(getLinkage) supports classes.

{
rt_finalize(cast(void*)obj);
}

/// ditto
void destroy(T)(T obj) if (is(T == interface))
{
destroy(cast(Object)obj);
static if (is(T == interface))
destroy(cast(Object) obj);
else
rt_finalize(cast(void*) obj);
}

/// Reference type demonstration
Expand Down Expand Up @@ -2968,7 +2996,7 @@ unittest
i = 1;
assert(i == 1); // `i` changed to `1`
destroy(i);
assert(i == 0); // `i` is back to its initial state `0`
assert(i == 1); // `i` stays unchanged
}

unittest
Expand Down Expand Up @@ -3026,18 +3054,35 @@ unittest
}
}

// Does calling destroy against an object of type T do anything?
private template hasElaborateDestroy(T)
{
// Note: __xdtor includes fields, __dtor doesn't.
static if (__traits(hasMember, T, "__xdtor"))
enum bool hasElaborateDestroy = true;
else static if (is(T : U[], U))
enum bool hasElaborateDestroy = hasElaborateDestroy!U;
else static if (is(T : U[n], U, size_t n))
enum bool hasElaborateDestroy = hasElaborateDestroy!U && n > 0;
else
enum bool hasElaborateDestroy = false;
}

/// ditto
void destroy(T)(ref T obj) if (is(T == struct))
{
_destructRecurse(obj);
() @trusted {
auto buf = (cast(ubyte*) &obj)[0 .. T.sizeof];
auto init = cast(ubyte[])typeid(T).initializer();
if (init.ptr is null) // null ptr means initialize to 0s
buf[] = 0;
else
buf[] = init[];
} ();
static if (hasElaborateDestroy!T)
{
obj.__xdtor();
() @trusted {
auto buf = (cast(ubyte*) &obj)[0 .. T.sizeof];
auto init = cast(ubyte[])typeid(T).initializer();
if (init.ptr is null) // null ptr means initialize to 0s
buf[] = 0;
else
buf[] = init[];
} ();
}
}

nothrow @safe @nogc unittest
Expand All @@ -3047,7 +3092,7 @@ nothrow @safe @nogc unittest
A a;
a.s = "asd";
destroy(a);
assert(a.s == "A");
assert(a.s == "asd");
}
{
static int destroyed = 0;
Expand Down Expand Up @@ -3080,10 +3125,51 @@ nothrow @safe @nogc unittest
}

/// ditto
void destroy(T : U[n], U, size_t n)(ref T obj) if (!is(T == struct))
void destroy(T : U[n], U, size_t n)(ref T obj)
{
destroy(obj[]);
}

/// ditto
void destroy(T : U[], U)(T obj)
{
foreach_reverse (ref e; obj[])
destroy(e);
static if (hasElaborateDestroy!T)
{
enum isNothrow = is(typeof(() nothrow { obj[0].__xdtor; }));
static if (isNothrow)
{
foreach_reverse (ref e; obj[])
destroy(e);
}
else
{
if (!obj.length)
return;

Exception response;
// Double-loop so we don't inefficiently use try in a loop.
bigloop:
for (size_t i = obj.length; ; )
{
try
{
for (;;)
{
if (i == 0) break bigloop;
destroy(obj[--i]);
}
}
catch (Exception e)
{
if (!response) response = e;
if (i == 0) break;
--i; // skip the failed object
}
}
// Only exit point from the function
if (response) throw response;
}
}
}

unittest
Expand All @@ -3092,7 +3178,7 @@ unittest
a[0] = 1;
a[1] = 2;
destroy(a);
assert(a == [ 0, 0 ]);
assert(a == [ 1, 2 ]);
}

unittest
Expand Down Expand Up @@ -3145,9 +3231,8 @@ unittest

/// ditto
void destroy(T)(ref T obj)
if (!is(T == struct) && !is(T == interface) && !is(T == class) && !_isStaticArray!T)
if (!is(T == struct) && !is(T == interface) && !is(T == class) && !_isStaticArray!T)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to accept an lvalue dynamic array, which is potentially going to override the array code above. I'm not 100% sure, because it's a specialization.

This may fail:

static struct S
{
    int x;
    ~this() {}
}

S[] arr = new S[5];
arr[] = S(42);
destroy(arr);
assert(arr[0].x == 0); // will this fail?

{
obj = T.init;
}

template _isStaticArray(T : U[N], U, size_t N)
Expand All @@ -3165,12 +3250,12 @@ unittest
{
int a = 42;
destroy(a);
assert(a == 0);
assert(a == 42);
}
{
float a = 42;
destroy(a);
assert(isnan(a));
assert(a == 42);
}
}

Expand Down