Thread overview
How to call destructor before free without dropping @nogc?
Aug 19, 2021
Bienlein
Aug 19, 2021
Bienlein
Aug 19, 2021
vit
Aug 19, 2021
Bienlein
Aug 19, 2021
evilrat
Aug 19, 2021
Tejas
Aug 19, 2021
Ferhat Kurtulmuş
Aug 19, 2021
evilrat
Aug 19, 2021
Ferhat Kurtulmuş
Aug 19, 2021
Ferhat Kurtulmuş
August 19, 2021

Hello,

I allocate some instance of class C manually and then free the memory again:

class C {
    int num;

    ~this() {
	writeln("~this");
    }
}

void foo() // @nogc
{
    auto mem = cast(C)malloc(__traits(classInstanceSize, C));
    auto c = emplace!(C)(mem);

    c.num = 789;

    destroy(c);
    free(cast(void*) c);
    c = null;
}

int main()
{
	foo();
}

The code above works well as the destructor of c in class C is called by destroy. Problem is that destroy cannot be used once function foo is annotated with @nogc. There seems to be no way round it.

What I want is to keep the function foo annotated with @nogc, but still have the destructor of C be called before free is called. Is there a way to call the destructor through meta programming or some kind of reflection so that I can create some generic function that calls the destructor and then free for any kind of class?

Thanks, Bienlein

August 19, 2021

On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:

>

Hello,

I allocate some instance of class C manually and then free the memory again:

class C {
    int num;

    ~this() {
	writeln("~this");
    }
}

void foo() // @nogc
{
    auto mem = cast(C)malloc(__traits(classInstanceSize, C));
    auto c = emplace!(C)(mem);

    c.num = 789;

    destroy(c);
    free(cast(void*) c);
    c = null;
}

int main()
{
	foo();
}

The code above works well as the destructor of c in class C is called by destroy. Problem is that destroy cannot be used once function foo is annotated with @nogc. There seems to be no way round it.

What I want is to keep the function foo annotated with @nogc, but still have the destructor of C be called before free is called. Is there a way to call the destructor through meta programming or some kind of reflection so that I can create some generic function that calls the destructor and then free for any kind of class?

Thanks, Bienlein

Oops, I just realized that you can also not call emplace when @nogc is present. Well that is at least consistent with not either being able to call destroy ;-).

So, I guess this means that you can forget about manually allocating and freeing some instance of a class and using @nogc as well. That's a pitty, @nogc was a good idea.

August 19, 2021

On Thursday, 19 August 2021 at 08:25:23 UTC, Bienlein wrote:

>

On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:

>

Hello,

I allocate some instance of class C manually and then free the memory again:

class C {
    int num;

    ~this() {
	writeln("~this");
    }
}

void foo() // @nogc
{
    auto mem = cast(C)malloc(__traits(classInstanceSize, C));
    auto c = emplace!(C)(mem);

    c.num = 789;

    destroy(c);
    free(cast(void*) c);
    c = null;
}

int main()
{
	foo();
}

The code above works well as the destructor of c in class C is called by destroy. Problem is that destroy cannot be used once function foo is annotated with @nogc. There seems to be no way round it.

What I want is to keep the function foo annotated with @nogc, but still have the destructor of C be called before free is called. Is there a way to call the destructor through meta programming or some kind of reflection so that I can create some generic function that calls the destructor and then free for any kind of class?

Thanks, Bienlein

Oops, I just realized that you can also not call emplace when @nogc is present. Well that is at least consistent with not either being able to call destroy ;-).

So, I guess this means that you can forget about manually allocating and freeing some instance of a class and using @nogc as well. That's a pitty, @nogc was a good idea.

Try this:

import std;
import core.stdc.stdlib : malloc, free;

class C {
    int num;

    ~this() @nogc{
		debug writeln("~this");
    }
}

void foo()  @nogc
{
    auto mem = cast(C)malloc(__traits(classInstanceSize, C));
    auto c = emplace!(C)(mem);

    c.num = 789;

    destruct(c);
    free(cast(void*) c);
    c = null;
}

void main()
{
	foo();
}



//https://github.com/atilaneves/automem/blob/master/source/automem/utils.d

void destruct(T)(T obj) if (is(T == class)) {
    (cast(_finalizeType!T) &rt_finalize)(() @trusted { return cast(void*) obj; }());
}


private extern(C){
	void rt_finalize(void* p, bool det = true) @trusted @nogc pure nothrow;

    template _finalizeType(T) {
        import std.traits: Unqual;
        static if (is(Unqual!T == Object)) {
            alias _finalizeType = typeof(&rt_finalize);
        } else {
            import std.traits : BaseClassesTuple;
            import std.meta : AliasSeq;
            alias _finalizeType = typeof(function void(void* p, bool det = true) {
                // generate a body that calls all the destructors in the chain,
                // compiler should infer the intersection of attributes
                foreach (B; AliasSeq!(T, BaseClassesTuple!T)) {
                    // __dtor, i.e. B.~this
                    static if (__traits(hasMember, B, "__dtor"))
                        () { B obj; obj.__dtor; } ();
                    // __xdtor, i.e. dtors for all RAII members
                    static if (__traits(hasMember, B, "__xdtor"))
                        () { B obj; obj.__xdtor; } ();
                }
            });
        }
    }
}
August 19, 2021

On Thursday, 19 August 2021 at 08:25:23 UTC, Bienlein wrote:

>

Oops, I just realized that you can also not call emplace when @nogc is present. Well that is at least consistent with not either being able to call destroy ;-).

So, I guess this means that you can forget about manually allocating and freeing some instance of a class and using @nogc as well. That's a pitty, @nogc was a good idea.

you are probably doing something wrong, could you try @nogc ctor?

anyway @nogc is way too limiting, I don't see why bother when there is already scope storage (should work in nogc) and -vgc flag to show possible allocations.


import core.lifetime;
import core.stdc.stdio;
import core.stdc.stdlib;

class SomeClass
{
    int a = 42;

    this() @nogc { }
    this(int val) @nogc { a = val; }
}



@nogc void main()
{
	byte[64] mem;
	mem.emplace!SomeClass();
	printf("stack %d\n", (cast(SomeClass) mem.ptr).a); // 42
	
	scope a = new SomeClass();
	printf("scope %d\n", a.a); //42
	
	SomeClass dynAlloc = cast(SomeClass) malloc(__traits(classInstanceSize, SomeClass));
	dynAlloc = emplace!SomeClass(dynAlloc, 123);
	printf("dynamic %d\n", dynAlloc.a); // 123
}
August 19, 2021

This works, vit. Thanks! I thought it wouldn't, because your code still makes use of embrace. But it somehow worked, although I don't understand why ... ;-).

I also added a constructor using the same approach as your destructor and this also worked:

 this(int otherNum) @nogc {
     this.num = otherNum;
     debug writeln("this: ", this.num);
 }

@evilrat: Will try what you suggested after work today. Too busy now.

August 19, 2021

On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:

>

Hello,

I allocate some instance of class C manually and then free the memory again:

[...]

I just wanted to leave this here.
https://github.com/AuburnSounds/Dplug/blob/master/core/dplug/core/nogc.d

August 19, 2021

On Thursday, 19 August 2021 at 15:12:03 UTC, Ferhat Kurtulmuş wrote:

>

On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:

>

Hello,

I allocate some instance of class C manually and then free the memory again:

[...]

I just wanted to leave this here.
https://github.com/AuburnSounds/Dplug/blob/master/core/dplug/core/nogc.d

This is cool, but even in unit tests for malloc wrapper there is only simple case with class without references to another class and no dtor.

Seems like the issue is that one have to add @nogc constructor/destructor overloads for emplace/destroy, and the author can't have @nogc dtor because of writeln (IIRC @nogc using GC is allowed with debug anyway), and all class members of another classes must recursively provide them as well.

August 19, 2021

On Thursday, 19 August 2021 at 09:39:26 UTC, evilrat wrote:

>

On Thursday, 19 August 2021 at 08:25:23 UTC, Bienlein wrote:

>

Oops, I just realized that you can also not call emplace when @nogc is present. Well that is at least consistent with not either being able to call destroy ;-).

So, I guess this means that you can forget about manually allocating and freeing some instance of a class and using @nogc as well. That's a pitty, @nogc was a good idea.

you are probably doing something wrong, could you try @nogc ctor?

anyway @nogc is way too limiting, I don't see why bother when there is already scope storage (should work in nogc) and -vgc flag to show possible allocations.


import core.lifetime;
import core.stdc.stdio;
import core.stdc.stdlib;

class SomeClass
{
    int a = 42;

    this() @nogc { }
    this(int val) @nogc { a = val; }
}



@nogc void main()
{
	byte[64] mem;
	mem.emplace!SomeClass();
	printf("stack %d\n", (cast(SomeClass) mem.ptr).a); // 42
	
	scope a = new SomeClass();
	printf("scope %d\n", a.a); //42
	
	SomeClass dynAlloc = cast(SomeClass) malloc(__traits(classInstanceSize, SomeClass));
	dynAlloc = emplace!SomeClass(dynAlloc, 123);
	printf("dynamic %d\n", dynAlloc.a); // 123
}

Allocating to a function local variable via new always allocates on stack assuming no arguments are passed to new

Read sentence 6 of https://dlang.org/spec/expression.html#new_expressions

So

scope a = new SomeClass();

actually allocates on stack

August 19, 2021

On Thursday, 19 August 2021 at 15:38:19 UTC, evilrat wrote:

>

On Thursday, 19 August 2021 at 15:12:03 UTC, Ferhat Kurtulmuş

>

This is cool, but even in unit tests for malloc wrapper there is only simple case with class without references to another class and no dtor.

If you examine the entire library, there are various use cases of nogc classes. For instance, a derived class containing references to other class objects 1. I am not using classes heavily with D. I just once happily used dplug's nogc facilities. When I saw this thread, I just wanted to share it here.

>

Seems like the issue is that one have to add @nogc constructor/destructor overloads for emplace/destroy, and the author can't have @nogc dtor because of writeln (IIRC @nogc using GC is allowed with debug anyway), and all class members of another classes must recursively provide them as well.

I agree with you. D needs more nogc facilities for OOP. It would not be so hard to include those overloads. Probably, this would violate the strictly defended safety principles of D?

August 19, 2021

On Thursday, 19 August 2021 at 15:38:19 UTC, evilrat wrote:

>

On Thursday, 19 August 2021 at 15:12:03 UTC, Ferhat Kurtulmuş

Btw, based on https://github.com/dlang/druntime/blob/master/src/object.d#L4209:

import core.lifetime;
import core.stdc.stdio;
import core.stdc.stdlib;

extern (C) void rt_finalize(void *data, bool det=true) @nogc nothrow; // cheap hack here
alias destroy = rt_finalize;

class SomeClass
{
    int a = 42;

    this() @nogc { }
    ~this() @nogc {printf("nogc\n");}
    this(int val) @nogc { a = val; }
}



@nogc void main()
{
	SomeClass dynAlloc = cast(SomeClass) malloc(__traits(classInstanceSize, SomeClass));
	dynAlloc = emplace!SomeClass(dynAlloc, 123);
	printf("dynamic %d\n", dynAlloc.a); // 123
    //rt_finalize(cast(void*)dynAlloc);
    destroy(cast(void*)dynAlloc); // cast needed :/ dunno consequences though
}