expectations 0.1.0

Vladimir Panteleev thecybershadow.lists at gmail.com
Mon Sep 3 00:52:39 UTC 2018


On Sunday, 2 September 2018 at 06:59:20 UTC, Paul Backus wrote:
> expectations is an error-handling library that lets you bundle 
> exceptions together with return values. It is based on Rust's 
> Result<T, E> [1] and C++'s proposed std::expected. [2] If 
> you're not familiar with those, Andrei's NDC Oslo talk, "Expect 
> the Expected" [3], explains the advantages of this approach to 
> error handling in considerable detail.

Sorry, I didn't watch the talk, but this sounds like something 
that's been on my mind for a while.

There are generally two classic approaches to error handling:

- Error codes. Plus: no overhead. Minus: you need to remember to 
check them. Some languages force you to check them, but it 
results in very noisy code in some cases (e.g. 
https://stackoverflow.com/a/3539342/21501).

- Exceptions. Plus: simple to use. Minus: unnecessary (and 
sometimes considerable) overhead when failure is not exceptional.

Now, Rust's Result works because it forces you to check the error 
code, and provides some syntax sugar to pass the result up the 
stack or abort if an error occurred. D, however, has nothing to 
force checking the return value of a function (except for pure 
functions, which is inapplicable for things like I/O).

Please correct me if I'm wrong, but from looking at the code, 
given e.g.:

Expected!void copyFile(string from, string to);

nothing prevents me from writing:

void main() { copyFile("nonexistent", "target"); }

The success value is silently discarded, so we end up with a "ON 
ERROR RESUME NEXT" situation again, like badly written C code.

One way we could improve on this in theory is to let functions 
return a successfulness value, which is converted into a thrown 
exception IFF the function failed AND the caller didn't check if 
an error occurred.

Draft implementation:

struct Success(E : Exception)
{
	private E _exception;
	private bool checked = false;
	@property E exception() { checked = true; return _exception; }
	@property ok() { return exception is null; }
	@disable this(this);
	~this() { if (_exception && !checked) throw _exception; }
}
Success!E failure(E)(E e) { return Success!E(e); }

Success!Exception copyFile(string from, string to)
{
	// dummy
	if (from == "nonexistent")
		return failure(new Exception("EEXIST"));
	else
		return typeof(return)();
}

void main()
{
	import std.exception;

	copyFile("existent", "target");
	assert(!copyFile("nonexistent", "target").ok);
	assertThrown!Exception({ copyFile("nonexistent", "target"); }());
}

This combines some of the advantages of the two approaches above. 
In the above draft I used a real exception object for the 
payload, constructing which still has a significant overhead (at 
least over an error code), though we do get rid of a try/catch if 
an error is not an exceptional situation. The advantage of using 
a real exception object is that its stack trace is generated when 
the exception is instantiated, and not when it's thrown, which 
means that the error location inside the copyFile implementation 
is recorded; but the same general idea would work with a 
numerical error code payload too.

Any thoughts?


More information about the Digitalmars-d-announce mailing list