Thread overview
Attributes lost in TypeInfo, please advise
Feb 12, 2015
Jakob Ovrum
Feb 12, 2015
Adam D. Ruppe
Feb 12, 2015
Jakob Ovrum
Feb 12, 2015
Benjamin Thaut
Feb 13, 2015
Jakob Ovrum
February 12, 2015
Consider the following:

---
struct S
{
    ~this() @safe {}
}

void foo() @safe
{
    S s;
    // OK
}

void bar() @safe
{
    S s;
    destroy(s);
    // test.d(15): Error: safe function 'test.bar' cannot call system function 'object.destroy!(S).destroy'
}
---

`destroy` is used in algorithms using low-level operations like `emplace[Ref]`, and while `destroy` itself is a template and thus enjoys attribute inference, it calls the non-generic typeid(T).destroy function, which is unconditionally @system. This unsafety then propagates all the way up to high-level code that is otherwise inferred to be safe.

The `postblit` TypeInfo method, used from `emplace[Ref]`, has the same problem.

Is it possible to call the destructor or postblit constructor directly, and will they correctly destruct/copy recursively like TypeInfo.destroy/postblit do? If so, we should change `destroy` and `emplaceImpl` to use direct calls ASAP.
February 12, 2015
On Thursday, 12 February 2015 at 04:08:23 UTC, Jakob Ovrum wrote:
> Is it possible to call the destructor or postblit constructor directly

yes, they are available as obj.__dtor() and obj.__postblit(); But...

> and will they correctly destruct/copy recursively

No.

extern(C)
@trusted void printf(const char*);
struct Child {
        @safe ~this() { printf("child dtor\n"); }
}
struct Parent {
        Child c;
        @safe ~this() { printf("parent dtor\n"); }
}
void main() @safe {
        Parent p;
        p.__dtor();
}


parent dtor // this is the one from the p.__dtor
   // note it did NOT run child dtor
parent dtor  // the natural destruction from going out of scope
child dtor  // ...which also calls the child



So you'd have to loop through all members in a custom destroy function and call them yourself. Then attribute inference should work.
February 12, 2015
On Thursday, 12 February 2015 at 04:18:06 UTC, Adam D. Ruppe wrote:
> On Thursday, 12 February 2015 at 04:08:23 UTC, Jakob Ovrum wrote:
>> Is it possible to call the destructor or postblit constructor directly
>
> yes, they are available as obj.__dtor() and obj.__postblit(); But...
>
>> and will they correctly destruct/copy recursively
>
> No.

Thanks.

> So you'd have to loop through all members in a custom destroy function and call them yourself. Then attribute inference should work.

I feared as much. I'll cook something up and send a PR for review.
February 12, 2015
On 2/11/15 11:08 PM, Jakob Ovrum wrote:
> Consider the following:
>
> ---
> struct S
> {
>      ~this() @safe {}
> }
>
> void foo() @safe
> {
>      S s;
>      // OK
> }
>
> void bar() @safe
> {
>      S s;
>      destroy(s);
>      // test.d(15): Error: safe function 'test.bar' cannot call system
> function 'object.destroy!(S).destroy'
> }
> ---
>
> `destroy` is used in algorithms using low-level operations like
> `emplace[Ref]`, and while `destroy` itself is a template and thus enjoys
> attribute inference, it calls the non-generic typeid(T).destroy
> function, which is unconditionally @system. This unsafety then
> propagates all the way up to high-level code that is otherwise inferred
> to be safe.
>
> The `postblit` TypeInfo method, used from `emplace[Ref]`, has the same
> problem.
>
> Is it possible to call the destructor or postblit constructor directly,
> and will they correctly destruct/copy recursively like
> TypeInfo.destroy/postblit do? If so, we should change `destroy` and
> `emplaceImpl` to use direct calls ASAP.

The reason typeid.destroy is used is because it does what you think calling the destructor directly should do. As Adam pointed out, __dtor does not do it right.

Note, there is a hidden function generated by the compiler, that TypeInfo.destroy maps to. There is no way to call it directly.

There is a bug report that shows why we do it that way (recall that destroy was once named clear): https://issues.dlang.org/show_bug.cgi?id=5667

I think given the necessity of the above (which was not discussed or noticed in that bug report), we should add a way to call the true destructor properly in the compiler.

-Steve
February 12, 2015
On Thursday, 12 February 2015 at 12:59:39 UTC, Steven Schveighoffer wrote:
>
> I think given the necessity of the above (which was not discussed or noticed in that bug report), we should add a way to call the true destructor properly in the compiler.
>
> -Steve

Yes please. Its also going to genereate more optimal code. Calling the destructor through the TypeInfo leads to two unnecessary indirections.

Kind Regards
Benjamin Thaut
February 13, 2015
On Thursday, 12 February 2015 at 12:59:39 UTC, Steven Schveighoffer wrote:
> I think given the necessity of the above (which was not discussed or noticed in that bug report), we should add a way to call the true destructor properly in the compiler.
>
> -Steve

I think these do the right thing with only marginal overhead:

---
void destructRecurse(S)(ref S s)
    if (is(S == struct))
{
    static if (__traits(hasMember, S, "__dtor"))
        s.__dtor();

    foreach_reverse (ref field; s.tupleof)
    {
        alias Field = typeof(field);
        static if (is(Field == struct) && hasElaborateDestructor!Field)
            destructRecurse(field);
    }
}

void postblitRecurse(S)(ref S s)
    if (is(S == struct))
{
    foreach (ref field; s.tupleof)
    {
        alias Field = typeof(field);
        static if (is(Field == struct) && hasElaborateCopyConstructor!Field)
            postblitRecurse(field);
    }

    static if (__traits(hasMember, S, "__postblit"))
        s.__postblit();
}
---
I notice now it is missing proper handling of fixed-length arrays: I'll add that. Anything else missing?

Efficiency-wise they should at least be a lot better than the status quo - two indirect calls. For absolutely optimal performance it relies on the inliner, but if it is demonstrated to be a problem compared to the compiler-generated solution, it could always generate optimal code with some hands-on string mixins :)

I am aware that std.traits is not available in object_.d - the hasElaborate* templates are fairly simple and easy to reimplement.
February 13, 2015
On 2/12/15 11:01 PM, Jakob Ovrum wrote:
> On Thursday, 12 February 2015 at 12:59:39 UTC, Steven Schveighoffer wrote:
>> I think given the necessity of the above (which was not discussed or
>> noticed in that bug report), we should add a way to call the true
>> destructor properly in the compiler.
>>
>
> I think these do the right thing with only marginal overhead:
>
> ---
> void destructRecurse(S)(ref S s)
>      if (is(S == struct))
> {
>      static if (__traits(hasMember, S, "__dtor"))
>          s.__dtor();
>
>      foreach_reverse (ref field; s.tupleof)
>      {
>          alias Field = typeof(field);
>          static if (is(Field == struct) && hasElaborateDestructor!Field)
>              destructRecurse(field);
>      }
> }
>
> void postblitRecurse(S)(ref S s)
>      if (is(S == struct))
> {
>      foreach (ref field; s.tupleof)
>      {
>          alias Field = typeof(field);
>          static if (is(Field == struct) &&
> hasElaborateCopyConstructor!Field)
>              postblitRecurse(field);
>      }
>
>      static if (__traits(hasMember, S, "__postblit"))
>          s.__postblit();
> }
> ---
> I notice now it is missing proper handling of fixed-length arrays: I'll
> add that. Anything else missing?
>
> Efficiency-wise they should at least be a lot better than the status quo
> - two indirect calls. For absolutely optimal performance it relies on
> the inliner, but if it is demonstrated to be a problem compared to the
> compiler-generated solution, it could always generate optimal code with
> some hands-on string mixins :)
>
> I am aware that std.traits is not available in object_.d - the
> hasElaborate* templates are fairly simple and easy to reimplement.

Thanks, but I wonder, aren't we simply duplicating what the compiler does? It seems kind of wasteful to have 2 identical functions. Plus, if any behavior changes, I'd rather have one place to change it. Last thing we need is subtle differences between builtin destruction and explicit destruction.

I really think either the compiler should be redirected to call this function (ideal) or we should be able to call the compiler function directly.

-Steve