Why exceptions for error handling is so important

Jacob Carlborg via Digitalmars-d digitalmars-d at puremagic.com
Tue Jan 13 01:10:57 PST 2015


On 2015-01-12 19:28, H. S. Teoh via Digitalmars-d wrote:

> While I agree with the general sentiment, I think the current convention
> of using a class hierarchy to implement exceptions is suboptimal.
>
> The problem with using a class hierarchy is that, like anything put into
> a hierarchy, some things just don't fit very well in a single-rooted
> hierarchy. This is especially true in D because there is no multiple
> inheritance (and for good reason too; multiple inheritance brings with
> it a whole set of nasty problems).
>
> A recent example was what to do with an exception that wraps around
> OS-level errors. From an implementor's POV, it makes sense to segregate
> exception types by implementation, that is, ErrnoException for Posix
> systems and SystemErrorCodeException (or some such) for Windows.
> However, this is totally useless to the end user: when you're traversing
> the filesystem, under this scheme you'd have to catch ErrnoException or
> catch SystemErrorCodeException (with a static-if on OS type, aka utter
> ugliness), and then ferret out the specific error code(s) you wish to
> handle, like what to do with an I/O error vs. a permission-denied error.
> Why should the *user* have to work with low-level implementation details
> like mapping Posix errno's and their corresponding Windows error codes
> to the semantic categories of real interest: i.e., access error /
> hardware failure / network error, etc.?
>
> So from the user's POV, the exception hierarchy ought to be semantically
> driven, rather than implementationally driven. Instead of ErrnoException
> and SystemErrorCodeException, one ought to have semantic categories like
> FileNotFoundException, AccessDeniedException, NetworkException, etc..
> However, this is burdensome on the implementor, because now a single
> underlying implementation like ErrnoException now has to be split across
> multiple unrelated semantic categories (FileNotFoundException must have
> an errno field on Posix and a systemErrorCode field on Windows, ditto
> for NetworkException, IOException, etc. -- and you can't factor it out
> into a base class because the class hierarchy is semantics driven, so
> ErrnoException doesn't fit anywhere in the hierarchy).
>
> Recently Dmitry (IIRC) came up with the rather clever idea of using
> interfaces instead of a single-rooted hierarchy for marking up semantic
> categories instead. Instead of trying to decide on whether we should
> have implementationally-driven OSException with subclasses
> ErrnoException and SystemErrorCodeException, or semantically-driven
> FileSystemException with subclasses FileNotFoundException and
> AccessDeniedException, we can have both: the class hierarchy itself
> (i.e. without the interfaces) uses base classes for factoring out
> implementation details, but tag each exception type with semantic
> categories derived from a distinct interface hierarchy. So we could have
> something like this:
>
> 	// Implementational hierarchy
> 	class Throwable { ... }
> 	class Exception : Throwable { ... }
> 	class ErrnoException : Exception {
> 		int errno;
> 		...
> 	}
> 	class SystemErrorCodeException : Exception {
> 		int systemErrorCode;
> 		...
> 	}
>
> 	// Semantic tags
> 	interface FilesystemException;
> 	interface FileNotFoundException : FilesystemException;
> 	interface AccessDeniedException : FilesystemException;
> 	..
>
> 	// Actual implementations
> 	static if (Posix) {
> 		// Note: you can even have multiple exception types that
> 		// inherit from FileNotFoundException, e.g., one thrown
> 		// by a manual stat() check, and one from a common
> 		// routine that interprets OS error codes. The user
> 		// wouldn't need to know the difference.
> 		class FileNotFoundExceptionImpl :
> 			ErrnoException,
> 			FileNotFoundException { ... }
> 		... // other Posix-specific exceptions here
> 	} else static if (Windows) {
> 		class FileNotFoundExceptionImpl :
> 			SystemErrorCodeException,
> 			FileNotFoundException { ... }
> 		... // other Windows-specific exceptions here
> 	}
>
> So now, user code doesn't have to worry about implementation details
> like whether the exception came from errno or a Windows error code, you
> can just catch semantic categories like FileNotFoundException instead.
>
> Currently, however, this scheme doesn't quite work because the compiler
> only allows catching classes derived from Throwable, and interfaces
> can't derive from non-interface base classes. Besides, I'm not sure the
> druntime exception catching code can deal with interfaces correctly as
> opposed to just base classes. But this is definitely an area that D
> could improve on!

I prefer a semantically driven hierarchy. I agree with you that the user 
should not need to care if the exception originated from D code with a 
native D exception or in C code with a error code converted to a D 
exception.

The reason this gets complicated seems to be the need for preserving the 
error code. Is that actually necessary? If all error codes are properly 
mapped to a D exception in a nice semantically driven hierarchy I'm not 
so sure if the error code is needed.

It seems to me that the interface approach is quite complicated on the 
implementation side with a static-if and all. But I guess that can be 
factored out.

I was thinking there could be one function that converts a given error 
code to a D exception. This hopefully only needs to be written once. 
Then it will be easy both on the user side and on the implementation side.

If you really do need the original error code, perhaps we could put that 
in a exception in Throwable.next.

The implementation side of some operation could look like this:

void openFile (in char* path)
{
     FILE* file = fopen(path, "w");
     if (!file)
         throwSystemException();
}

The implementation of throwSystemException would be something like:

void throwSystemException ()
{
     static if (Posix)
         auto errorCode = errno();

     else
         auto errorCode = GetLastError();

     throw errorCodeToThrowable(errorCode);
}

Throwable errorCodeToThrowable (int errorCode)
{
     Throwable throwable;

     static if (Posix)
     {
         switch (errorCode)
         {
             case ENOENT: thorwable = new FileNotFoundException;
             ...
         }

         // not sure if this is necessary
         throwable.next = new ErrnoException(errorCode);
     }
     else { ... }

     return throwable;
}

-- 
/Jacob Carlborg


More information about the Digitalmars-d mailing list