Ali Çehreli
Posted in reply to Ali Çehreli
| 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
|