Thread overview
New D technique - disable usage of "new" and "delete"
Jan 05, 2018
Basile B.
Jan 05, 2018
jmh530
Jan 05, 2018
Basile B.
Jan 08, 2018
jmh530
January 05, 2018
I was chatting on IRC. One of the topic was about a "@nogc" thread. I suggested something like "core.thread.NoGcThread" that would take a "@nogc" function but then i realized that this hypothetical class could still be allocated on the GC heap.
So "new" and "delete" would have to be disabled...Despite of being deprecated it appears that class allocators and deallocators can be used for that purpose, without compiler changes:

---
class Foo
{
    @disable new (size_t size){return null;}
    @disable delete (void* p){}
}

void main()
{
    static assert(!__traits(compiles, new Foo));
    import std.experimental.allocator;
    import std.experimental.allocator.mallocator;

    auto f = make!Foo(Mallocator.instance);
    dispose(Mallocator.instance, f);

    // auto g = new Foo, // doesn't work
}
---

What's the point ?

- It's fun to discover that.
- Libraries that don't want their aggregates to reside on the GC heap can use this, with a mixin. (note: although the GCAllocator can still be used...)


January 05, 2018
On Friday, 5 January 2018 at 19:44:38 UTC, Basile B. wrote:
> [snip]
> - Libraries that don't want their aggregates to reside on the GC heap can use this, with a mixin. (note: although the GCAllocator can still be used...)

Cool.

Not sure I'm following with your point with the mixin. A mixin like below works fine, but it would be a little annoying to get the constructors working. The only way I can think of is to pass them as strings to the create function.

import std.experimental.allocator;
import std.experimental.allocator.mallocator;

class Foo
{
    int a;
    @disable new (size_t size){return null;}
    @disable delete (void* p){}
}

string create(T, string name)()
{
    import std.conv : to;

    return T.stringof ~ " " ~ name ~ " = make!" ~ T.stringof ~
                                              "(Mallocator.instance);\n" ~
        "scope(exit) dispose(Mallocator.instance, " ~ name ~ ");";
}

void main()
{
    mixin(create!(Foo, "f"));
    f.a = 1;
}
January 05, 2018
On Friday, 5 January 2018 at 20:45:20 UTC, jmh530 wrote:
> On Friday, 5 January 2018 at 19:44:38 UTC, Basile B. wrote:
>> [snip]
>> - Libraries that don't want their aggregates to reside on the GC heap can use this, with a mixin. (note: although the GCAllocator can still be used...)
>
> Cool.
>
> Not sure I'm following with your point with the mixin. A mixin like below works fine, but it would be a little annoying to get the constructors working. The only way I can think of is to pass them as strings to the create function.
>
> import std.experimental.allocator;
> import std.experimental.allocator.mallocator;
>
> class Foo
> {
>     int a;
>     @disable new (size_t size){return null;}
>     @disable delete (void* p){}
> }
>
> string create(T, string name)()
> {
>     import std.conv : to;
>
>     return T.stringof ~ " " ~ name ~ " = make!" ~ T.stringof ~
>                                               "(Mallocator.instance);\n" ~
>         "scope(exit) dispose(Mallocator.instance, " ~ name ~ ");";
> }
>
> void main()
> {
>     mixin(create!(Foo, "f"));
>     f.a = 1;
> }

Yeah, i didn't explain correctly the mixin thing.
The mixin would be used to disable "new" and "delete", e.g

    enum disableNewAndDelete = "@disable new (size_t size){return null;} @disable delete (void* p){}";

and then you mix it in the classes of the framework that doesn't want "new" and "delete" to be used:

    mixin(disableNewAndDelete);
January 08, 2018
On Friday, 5 January 2018 at 20:51:14 UTC, Basile B. wrote:
>
> Yeah, i didn't explain correctly the mixin thing.
> The mixin would be used to disable "new" and "delete", e.g
>
>     enum disableNewAndDelete = "@disable new (size_t size){return null;} @disable delete (void* p){}";
>
> and then you mix it in the classes of the framework that doesn't want "new" and "delete" to be used:
>
>     mixin(disableNewAndDelete);

It occurs to me that you can use this to disable unsafe features (pointer arithmetic, casting immutability and shared most notably). For instance, something like below as a rough sketch (I didn't check that it compiles)

struct Safe(T)
{
    import std.traits : isPointer, Unqual;

    T _data;
    alias _data this;

    @disable this();

    this(T x) { _data = x; }

    @property T data()
    {
        return _data;
    }

    @property void data(T x)
    {
        _data = x;
    }

    static if (isPointer!T)
    {
        @disable U opCast(U)()
            if (isPointer!U && U != void*)
        @disable T opBinary(string op)(T rhs)
        @disable T opUnary(string op)()
        //also @disable opIndex, opDollar, opIndexUnary, opIndexAssign, ...
        //opIndexOpAssign
    }
    static if (isMutable!T)
    {
        @disable immutable(T) opCast(immutable(T))()
    }
    else
    {
        @disable Unqual!T opCast(Unqual!T)()
    }
    //add @disable for shared
}