In the Q&A of Átila’s talk at DConf at 1:13:45, someone asks if allocating and throwing exceptions might be a the case where you wouldn’t mind the GC even in supposed @nogc
code: When an Error
is thrown, the application is doomed anyways; when an Exception
is thrown, you already subscribed to inefficient execution.
There’s a few options:
- Ignore the issue at a language level and just lie (code below): Make an
Exception
allocating function and cast it to@nogc
. I do not know if that is UB. - Make
@nogc
not apply to aThrowable
allocated in athrow
expression. - Introduce Yet Another Damn Function Attribute (YADMA)
@nogcUnlessThrown
.
The code to lie:
template throw_new(E : Throwable, Args...)
{
alias doAllocate = function E(Args args) => new E(args);
noreturn throw_new(Args args) @nogc
{
import std.algorithm.comparison : among;
enum isSafe = "@safe".among(__traits(getFunctionAttributes, doAllocate)) > 0;
enum isPure = "pure".among(__traits(getFunctionAttributes, doAllocate)) > 0;
alias FP = mixin("E function(Args) @nogc ",
isSafe ? "@safe " : "", isPure ? "pure" : "");
immutable hackedAllocate = (() @trusted => cast(FP)(doAllocate))();
throw hackedAllocate(args);
}
}
void test() @nogc @safe pure
{
import std.format : FormatException;
throw_new!FormatException("msg");
}
void main() @safe
{
import std.format : FormatException;
try
{
test();
}
catch (FormatException e)
{
import std.stdio;
writeln(e.msg);
}
}
Some explanation for why it is like this:
- One has to use
alias doAllocate = function E(Args args) => new E(args);
instead of a normal function definition because a normal function definition does not infer attributes.
throw_new
has attributes inferred. isSafe
andisPure
take care that those attributes are carried through ifE
’s constructor happens to have them.hackedAllocate
is created via a@trusted
block because adding@nogc
is not@safe
. It only trusts the cast, not the call.- Perfect forwarding for the arguments is not trivial (I tried), but probably not needed anyway.