1 /// A draft of a better way to do exceptions 2 module arsd.exception; 3 /* 4 Exceptions 2.0 5 */ 6 7 interface ThrowableBase { 8 void fly(string file = __FILE__, size_t line = __LINE__); // should be built into the compiler's throw statement 9 10 // override these as needed 11 void printMembers(scope void delegate(in char[]) sink) const; // Tip: use mixin PrintMembers; instead of doing it yourself 12 void getHumanReadableMessage(scope void delegate(in char[]) sink) const; // the exception name should give this generally but it is nice if you have an error code that needs translation or something else that isn't obvious from the name 13 void printName(scope void delegate(in char[]) sink) const; // only need to override this if you aren't happy with RTTI's name field 14 15 // just call this when you are ready 16 void toString(scope void delegate(in char[]) sink) const; 17 } 18 19 mixin template ThrowableBaseImplementation() { 20 // This sets file and line at the throw point instead of in the ctor 21 // thereby separating allocation from error information - call this and 22 // file+line will be set then allowing you to reuse exception objects easier 23 void fly(string file = __FILE__, size_t line = __LINE__) { 24 this.file = file; 25 this.line = line; 26 throw this; 27 } 28 29 // You don't really need this - the class name and members should give all the 30 // necessary info, but it can be nice in cases like a Windows or errno exception 31 // where the code isn't necessarily as at-a-glance easy as the string from GetLastError. 32 /* virtual */ void getHumanReadableMessage(scope void delegate(in char[]) sink) const { 33 sink(msg); // for backward compatibility 34 } 35 36 // This prints the really useful info to the user, the members' values. 37 // You don't have to write this typically, instead use the mixin below. 38 /* virtual */ void printMembers(scope void delegate(in char[]) sink) const { 39 // this is done with the mixin from derived classes 40 } 41 42 /* virtual */ void printName(scope void delegate(in char[]) sink) const { 43 sink(typeid(this).name); // FIXME: would be nice if eponymous templates didn't spew the name twice 44 } 45 46 override void toString(scope void delegate(in char[]) sink) const { 47 char[32] tmpBuff = void; 48 printName(sink); 49 sink("@"); sink(file); 50 sink("("); sink(line.sizeToTempString(tmpBuff[])); sink(")"); 51 sink(": "); getHumanReadableMessage(sink); 52 sink("\n"); 53 printMembers(sink); 54 if (info) { 55 try { 56 sink("----------------"); 57 foreach (t; info) { 58 sink("\n"); sink(t); 59 } 60 } 61 catch (Throwable) { 62 // ignore more errors 63 } 64 } 65 } 66 67 } 68 69 class ExceptionBase : Exception, ThrowableBase { 70 // Hugely simplified ctor - nothing is even needed 71 this() { 72 super(""); 73 } 74 75 mixin ThrowableBaseImplementation; 76 } 77 78 class ErrorBase : Error, ThrowableBase { 79 this() { super(""); } 80 mixin ThrowableBaseImplementation; 81 } 82 83 // Mix this into your derived class to print all its members automatically for easier debugging! 84 mixin template PrintMembers() { 85 override void printMembers(scope void delegate(in char[]) sink) const { 86 foreach(memberName; __traits(derivedMembers, typeof(this))) { 87 static if(is(typeof(__traits(getMember, this, memberName))) && !is(typeof(__traits(getMember, typeof(this), memberName)) == function)) { 88 sink("\t"); 89 sink(memberName); 90 sink(" = "); 91 static if(is(typeof(__traits(getMember, this, memberName)) : const(char)[])) 92 sink(__traits(getMember, this, memberName)); 93 else static if(is(typeof(__traits(getMember, this, memberName)) : long)) { 94 char[32] tmpBuff = void; 95 sink(sizeToTempString(__traits(getMember, this, memberName), tmpBuff)); 96 } // else pragma(msg, typeof(__traits(getMember, this, memberName))); 97 sink("\n"); 98 } 99 } 100 101 super.printMembers(sink); 102 } 103 } 104 105 // The class name SHOULD obviate this but you can also add another message if you like. 106 // You can also just override the getHumanReadableMessage yourself in cases like calling strerror 107 mixin template StaticHumanReadableMessage(string s) { 108 override void getHumanReadableMessage(scope void delegate(in char[]) sink) const { 109 sink(s); 110 } 111 } 112 113 114 115 116 /* 117 Enforce 2.0 118 */ 119 120 interface DynamicException { 121 /* 122 TypeInfo getArgumentType(size_t idx); 123 void* getArgumentData(size_t idx); 124 string getArgumentAsString(size_t idx); 125 */ 126 } 127 128 template enforceBase(ExceptionBaseClass, string failureCondition = "ret is null") { 129 auto enforceBase(alias func, string file = __FILE__, size_t line = __LINE__, T...)(T args) { 130 auto ret = func(args); 131 if(mixin(failureCondition)) { 132 class C : ExceptionBaseClass, DynamicException { 133 T args; 134 this(T args) { 135 this.args = args; 136 } 137 138 override void printMembers(scope void delegate(in char[]) sink) const { 139 import std.traits; 140 import std.conv; 141 foreach(idx, arg; args) { 142 sink("\t"); 143 sink(ParameterIdentifierTuple!func[idx]); 144 sink(" = "); 145 sink(to!string(arg)); 146 sink("\n"); 147 } 148 sink("\treturn value = "); 149 sink(to!string(ret)); 150 sink("\n"); 151 } 152 153 override void printName(scope void delegate(in char[]) sink) const { 154 sink(__traits(identifier, ExceptionBaseClass)); 155 } 156 157 override void getHumanReadableMessage(scope void delegate(in char[]) sink) const { 158 sink(__traits(identifier, func)); 159 sink(" call failed"); 160 } 161 } 162 163 auto exception = new C(args); 164 exception.file = file; 165 exception.line = line; 166 throw exception; 167 } 168 169 return ret; 170 } 171 } 172 173 /// Raises an exception given a set of local variables to print out 174 void raise(ExceptionBaseClass, T...)(string file = __FILE__, size_t line = __LINE__) { 175 class C : ExceptionBaseClass, DynamicException { 176 override void printMembers(scope void delegate(in char[]) sink) const { 177 import std.conv; 178 foreach(idx, arg; T) { 179 sink("\t"); 180 sink(__traits(identifier, T[idx])); 181 sink(" = "); 182 sink(to!string(arg)); 183 sink("\n"); 184 } 185 } 186 187 override void printName(scope void delegate(in char[]) sink) const { 188 sink(__traits(identifier, ExceptionBaseClass)); 189 } 190 } 191 192 auto exception = new C(); 193 exception.file = file; 194 exception.line = line; 195 throw exception; 196 } 197 198 const(char)[] sizeToTempString(long size, char[] buffer) { 199 size_t pos = buffer.length - 1; 200 bool negative = size < 0; 201 if(size < 0) 202 size = -size; 203 while(size) { 204 buffer[pos] = size % 10 + '0'; 205 size /= 10; 206 pos--; 207 } 208 if(negative) { 209 buffer[pos] = '-'; 210 pos--; 211 } 212 return buffer[pos + 1 .. $]; 213 } 214 215 ///////////////////////////// 216 /* USAGE EXAMPLE FOLLOWS */ 217 ///////////////////////////// 218 219 220 // Make sure there's sane base classes for things that take 221 // various types. For example, RangeError might be thrown for 222 // any type of index, but we might just catch any kind of range error. 223 // 224 // The base class gives us an easy catch point for the category. 225 class MyRangeError : ErrorBase { 226 // unnecessary but kinda nice to have static error message 227 mixin StaticHumanReadableMessage!"Index out of bounds"; 228 } 229 230 // Now, we do a new class for each error condition that can happen 231 // inheriting from a convenient catch-all base class for our error type 232 // (which might be ExceptionBase itself btw) 233 class TypedRangeError(T) : MyRangeError { 234 // Error details are stored as DATA MEMBERS 235 // do NOT convert them to a string yourself 236 this(T index) { 237 this.index = index; 238 } 239 240 mixin StaticHumanReadableMessage!(T.stringof ~ " index out of bounds"); 241 242 // The data members can be easily inspected to learn more 243 // about the error, perhaps even to retry it programmatically 244 // and this also avoids the need to do something like call to!string 245 // and string concatenation functions at the construction point. 246 // 247 // Yea, this gives more info AND is allocation-free. What's not to love? 248 // 249 // Templated ones can be a pain just because of the need to specify it to 250 // catch or cast, but it will always at least be available in the printed string. 251 T index; 252 253 // Then, mixin PrintMembers uses D's reflection to do all the messy toString 254 // data sink nonsense for you. Do this in each subclass where you add more 255 // data members (which out to be generally all of them, more info is good. 256 mixin PrintMembers; 257 } 258 259 version(exception_2_example) { 260 261 // We can pass pre-constructed exceptions to functions and get good file/line and stacktrace info! 262 void stackExample(ThrowableBase exception) { 263 // throw it now (custom function cuz I change the behavior a wee bit) 264 exception.fly(); // ideally, I'd change the throw statement to call this function for you to set up line and file 265 } 266 267 void main() { 268 int a = 230; 269 string file = "lol"; 270 static class BadValues : ExceptionBase {} 271 //raise!(BadValues, a, file); 272 273 alias enforce = enforceBase!ExceptionBase; 274 275 import core.stdc.stdio; 276 auto fp = enforce!fopen("nofile.txt".ptr, "rb".ptr); 277 278 279 // construct, passing it error details as data, not strings. 280 auto exception = new TypedRangeError!int(4); // exception construction is separated from file/line setting 281 stackExample(exception); // so you can allocate/construct in one place, then set and throw somewhere else 282 } 283 }