The OpenD Programming Language

ArsdExceptionBase

Base class representing my exceptions. You should almost never work with this directly, but you might catch it as a generic thing. Catch it before generic object.Exception or object.Throwable in any catch chains.

Constructors

this
this(string operation, string file, size_t line, Throwable next)

Don't call this except from other exceptions; this is essentially an abstract class.

Members

Functions

getAdditionalPrintableInformation
void getAdditionalPrintableInformation(void delegate(string name, in char[] value) sink)

Users might like to see additional information with the exception. API consumers should pull this out of properties on your child class, but the parent class might not be able to deal with the arbitrary types at runtime the children can introduce, so bringing them all down to strings simplifies that.

msg
void msg()

deliberately hiding Throwable.msg. Use message and toString instead.

printableExceptionName
string printableExceptionName()

This is the name of the exception class, suitable for printing. This should be static data (e.g. a string literal). Override it in subclasses.

toString
void toString(void delegate(in char[]) sink)
string toString()

The toString method will print out several components:

Detailed Description

General guidelines for exceptions

The purpose of an exception is to cancel a task that has proven to be impossible and give the programmer enough information to use at a higher level to decide what to do about it.

Cancelling a task is accomplished with the throw keyword. The transmission of information to a higher level is done by the language runtime. The decision point is marked by the catch keyword. The part missing - the job of the Exception class you construct and throw - is to gather the information that will be useful at a later decision point.

It is thus important that you gather as much useful information as possible and keep it in a way that the code catching the exception can still interpret it when constructing an exception. Other concerns are secondary to this to this primary goal.

With this in mind, here's some guidelines for exception handling in arsd code.

Allocations and lifetimes

Don't get clever with exception allocations. You don't know what the catcher is going to do with an exception and you don't want the error handling scheme to introduce its own tricky bugs. Remember, an exception object's first job is to deliver useful information up the call chain in a way this code can use it. You don't know what this code is or what it is going to do.

Keep your memory management schemes simple and let the garbage collector do its job.

  • All thrown exceptions should be allocated with the new keyword.
  • Members inside the exception should be value types or have infinite lifetime (that is, be GC managed).
  • While this document is concerned with throwing, you might want to add additional information to an in-flight exception, and this is done by catching, so you need to know how that works too, and there is a global compiler switch that can change things, so even inside arsd we can't completely avoid its implications.

    DIP1008's presence complicates things a bit on the catch side - if you catch an exception and return it from a function, remember to ex.refcount = ex.refcount + 1; so you don't introduce more use-after-free woes for those unfortunate souls.

Error strings

Strings can deliver useful information to people reading the message, but are often suboptimal for delivering useful information to other chunks of code. Remember, an exception's first job is to be caught by another block of code. Printing to users is a last resort; even if you want a user-readable error message, an exception is not the ideal way to deliver one since it is constructed in the guts of a failed task, without the higher level context of what the user was actually trying to do. User error messages ought to be made from information in the exception, combined with higher level knowledge. This is best done in a catch block, not a throw statement.

As such, I recommend that you:

  • Don't concatenate error strings at the throw site. Instead, pass the data you would have used to build the string as actual data to the constructor. This lets catchers see the original data without having to try to extract it from a string. For unique data, you will likely need a unique exception type. More on this in the next section.
  • Don't construct error strings in a constructor either, for the same reason. Pass the useful data up the call chain, as exception members, to the maximum extent possible. Exception: if you are passed some data with a temporary lifetime that is important enough to pass up the chain. You may .idup or to!string to preserve as much data as you can before it is lost, but still store it in a separate member of the Exception subclass object.
  • Do construct strings out of public members in getAdditionalPrintableInformation. When this is called, the user has requested as much relevant information as reasonable in string format. Still, avoid concatenation - it lets you pass as many key/value pairs as you like to the caller. They can concatenate as needed. However, note the words "public members" - everything you do in getAdditionalPrintableInformation ought to also be possible for code that caught your exception via your public methods and properties.

Subclasses

Any exception with unique data types should be a unique class. Whenever practical, this should be one you write and document at the top-level of a module. But I know we get lazy - me too - and this is why in standard D we'd often fall back to throw new Exception("some string " ~ some info). To help resist these urges, I offer some helper functions to use instead that better achieve the key goal of exceptions - passing structured data up a call chain - while still being convenient to write.

See: ArsdException, Win32Enforce

Meta