So I ran across this (IMHO) very nice idea of using a composite structure of a return value and an exception - Expected<T>
. It overcomes many shortcomings of the traditional methods of error handling (exceptions, error codes).
See the Andrei Alexandrescu's talk (Systematic Error Handling in C++) and its slides.
The exceptions and error codes have basically the same usage scenarios with functions that return something and the ones that don't. Expected<T>
, on the other hand, seems to be targeted only at functions that return values.
So, my questions are:
- Have any of you tried
Expected<T>
in practice? - How would you apply this idiom to functions returning nothing (that is, void functions)?
Update:
I guess I should clarify my question. The Expected<void>
specialization makes sense, but I'm more interested in how it would be used - the consistent usage idiom. The implementation itself is secondary (and easy).
For example, Alexandrescu gives this example (a bit edited):
string s = readline();
auto x = parseInt(s).get(); // throw on error
auto y = parseInt(s); // won’t throw
if (!y.valid()) {
// ...
}
This code is "clean" in a way that it just flows naturally. We need the value - we get it. However, with expected<void>
one would have to capture the returned variable and perform some operation on it (like .throwIfError()
or something), which is not as elegant. And obviously, .get()
doesn't make sense with void.
So, what would your code look like if you had another function, say toUpper(s)
, which modifies the string in-place and has no return value?
Like Matthieu M. said, this is something relatively new to C++, but nothing new for many functional languages.
I would like to add my 2 cents here: part of the difficulties and differences are can be found, in my opinion, in the "procedural vs. functional" approach. And I would like to use Scala (because I am familiar both with Scala and C++, and I feel it has a facility (Option) which is closer to
Expected<T>
) to illustrate this distinction.In Scala you have Option[T], which is either Some(t) or None. In particular, it is also possible to have Option[Unit], which is morally equivalent to
Expected<void>
.In Scala, the usage pattern is very similar and built around 2 functions: isDefined() and get(). But it also have a "map()" function.
I like to think of "map" as the functional equivalent of "isDefined + get":
becomes
"propagating" the option to the result
I think that here, in this functional style of using and composing options, lies the answer to your question:
Personally, I would NOT modify the string in place, or at least I will not return nothing. I see
Expected<T>
as a "functional" concept, that need a functional pattern to work well: toUpper(s) would need to either return a new string, or return itself after modification:or, with a Scala-like map
if you don't want to follow a functional route, you can just use isDefined/valid and write your code in a more procedural way:
If you follow this route (maybe because you need to), there is a "void vs. unit" point to make: historically, void was not considered a type, but "no type" (void foo() was considered alike a Pascal procedure). Unit (as used in functional languages) is more seen as a type meaning "a computation". So returning a Option[Unit] does make more sense, being see as "a computation that optionally did something". And in
Expected<void>
, void assumes a similar meaning: a computation that, when it does work as intended (where there are no exceptional cases), just ends (returning nothing). At least, IMO!So, using Expected or Option[Unit] could be seen as computations that maybe produced a result, or maybe not. Chaining them will prove it difficult:
Not very clean.
Map in Scala makes it a little bit cleaner
Which is better, but still far from ideal. Here, the Maybe monad clearly wins... but that's another story..
Even though it might appear new for someone focused solely on C-ish languages, to those of us who had a taste of languages supporting sum-types, it's not.
For example, in Haskell you have:
Where the
|
reads or and the first element (Nothing
,Just
,Left
,Right
) is just a "tag". Essentially sum-types are just discriminating unions.Here, you would have
Expected<T>
be something like:Either T Exception
with a specialization forExpected<void>
which is akin toMaybe Exception
.I've been pondering the same question since I've watched this video. And so far I didn't find any convincing argument for having Expected, for me it looks ridiculous and against clarity&cleanness. I have come up with the following so far:
noexcept
. Every.noexcept
should be wrapped by try{}catch{}If those statements hold then we have self-documented easy to use interfaces with only one drawback: we don't know what exceptions could be thrown without peeking into implementation details.
Expected impose some overheads to the code since if you have some exception in the guts of your class implementation(e.g. deep inside private methods) then you should catch it in your interface method and return Expected. While I think it is quite tolerable for the methods which have a notion for returning something I believe it brings mess and clutter to the methods which by design have no return value. Besides for me it is quite unnatural to return thing from something that is not supposed to return anything.
It's quite natural, I used it even before I saw this talk.
The form presented in the slides has some subtle implications:
This does not hold if you have
expected<void>
, because since nobody is interested in thevoid
value the exception is always ignored. I would force this as I would force reading fromexpected<T>
in Alexandrescus class, with assertions and an explicitsuppress
member function. Rethrowing the exception from the destructor is not allowed for good reasons, so it has to be done with assertions.