Contract error string generation is not implicitly @nogc

Ali Çehreli acehreli at yahoo.com
Mon Sep 5 21:54:35 UTC 2022


First of all, I messed up the subject line. I didn't mean "implicit 
@nogc" but "@nogc should still allow allocation" for contract strings.

On 9/4/22 07:06, Ali Çehreli wrote:

 > we are about to throw an Error,
 > which is supposed to imply we shouldn't continue with the program
 > anyway.

I went ahead and over-engineered a solution which is probably broken in 
some important way but I am happy with it. :)

The trick is, instead of making an error string right there and then, an 
Error is thrown, which contains some data. The GC is used later when 
error data is being used. Good luck if data has a copy constructor which 
needs the GC. :p

This is a best-effort mechanism because there is a pre-thread instance 
for that specific Error, which carries error data.

Here is how it works:

The programmer defines an Error with a distinguishing string tag. The 
SumType definition must list all kinds of data that such an error can 
carry. (Luckily, a compile-time error instructs the programmer about the 
missing Tuple types that need to be added.)

mixin NoGcError!("Foo",
                  SumType!(Tuple!(Foo, int, int),
                           Tuple!(string),
                           Tuple!()));

Although "Foo" could be anything, it makes sense to associate it e.g. 
with a struct:

struct Foo {
   int i;


The user must specify "Foo" when producing the error string:

   void bar(int i, int j) @safe pure nothrow @nogc
   in (i == 42, error!"Foo"("Something is not right", this, i, j)) {
     // ...
   }


Note different type of error data here:

   void zar(string str, double d) @safe pure nothrow @nogc
   in (!isNaN(d), error!"Foo"("Must not be nan", str)) {
     // ...
   }
}

Of course, error!"Foo" can be used to throw outside of contracts as well:

void main() {
   auto f = Foo(1);
   // f.bar(42, 44);
   // f.zar("hello world", double.init);
   error!"Foo"("Wroing!");
}

Here is a complete draft:

```D
import std; // Sorry :(

mixin NoGcError!("Foo",
                  SumType!(Tuple!(Foo, int, int),
                           Tuple!(string),
                           Tuple!()));

struct Foo {
   int i;

   void bar(int i, int j) @safe pure nothrow @nogc
   in (i == 42, error!"Foo"("Something is not right", this, i, j)) {
     // ...
   }

   void zar(string str, double d) @safe pure nothrow @nogc
   in (!isNaN(d), error!"Foo"("Must not be nan", str)) {
     // ...
   }
}

void main() {
   auto f = Foo(1);
   // f.bar(42, 44);
   // f.zar("hello world", double.init);
   error!"Foo"("Wroing!");
}

mixin template NoGcError(string tag, Data) {
   class NoGcError : Error {
     string msg;
     Data data;

     enum noDataString = Tuple!()().to!string;

     this() {
       super(tag ~ " Error");
     }

     // Adapted from object.Throwable.toString
     override
     void toString(scope void delegate(in char[]) sink) const {
       sink(file); sink(":"); sink(line.to!string); sink(": ");
       sink(tag); sink(" Error: "); sink(msg);
       const dataStr = data.to!string;
       if (dataStr != noDataString) {
         sink("\n  Data: "); sink(dataStr);
       }

       if (info) {
         try {
           sink("\n----------------");
           foreach (t; info) {
             sink("\n"); sink(t);
           }

         } catch (Throwable) {
           // ignore more errors
         }
       }
     }
   }

   static ref error_(string t)()
   if (t == tag) {
     static NoGcError err_;
     return err_;
   }

   static this() {
     error_!tag = new NoGcError();
   }
}

string error(string tag, Data...)(string msg,
                                   Data data,
                                   string file = __FILE__,
                                   size_t line = __LINE__) @safe pure 
nothrow @nogc {

   static auto thrower(string msg, Data data, string file, size_t line) 
@trusted nothrow @nogc {
     static assert (__traits(compiles, error_!tag.data = tuple(data)),
                    format!`SumType of NoGcError!"%s" must include 
Tuple!%s`(tag, Data.stringof));

     error_!tag.msg = msg;
     error_!tag.data = tuple(data);
     error_!tag.file = file;
     error_!tag.line = line;
     throw error_!tag;
   }

   // Adapted from std/regex/internal/ir.d
   static assumePureFunction(T)(T t) @trusted pure nothrow @nogc {
     enum attrs = functionAttributes!T | FunctionAttribute.pure_;
     return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
   }

   assumePureFunction(&thrower)(msg, data, file, line);

   assert(false);
}
```

Ali



More information about the Digitalmars-d mailing list