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