2017-06-20 22:44 GMT+02:00 Emil Dotchevski via Boost
On Tue, Jun 20, 2017 at 2:16 AM, Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
3. I want validation failers to be handled immediately: one level up the stack. I do not expect or intend to ever propagate them further.
You can catch exceptions one level up if you want to. Right? :)
I can. And it would work. But it just feels not the right tool for the job. It would not reflect my intention as clearly as `outcome`.
That's because (in your mind, as you stated) you're not using Outcome to handle "real" errors.
Maybe you are right. Of course "real" and "unreal" are very subjective,
"Subjective", as in sometimes the correct design is not obvious or there are competing design goals which make it difficult or impossible for the library to define universal postconditions for a function, as it is often the case in boost::filesystem. In this case, something like Outcome/Noexcept helps, by shifting the responsibility to use the correct postconditions to the user. But this is not typical.
When writing a low-level asynchronous library like AFIO, situations like not being able to open a file or write to it at a given moment should not be treated as a "real error", because at this level, in this context, there is no corresponding postcondition.
Usually there is. The postcondition for a write function is that the data has been successfully submitted to the file system, because it is very rare that the user wouldn't care, in which case he can write a wrapper that ignores all errors.
Yes, users would care: and for them it makes sense to expose the interface with exceptions. For the implementer of the guts, he might consider it a "regular" situation and not engage postconditions.
But still, a dedicated library for representing variant return values is needed. and `variant` is not good enough.
The need is for an error handling library that doesn't use exceptions -- how exactly it works is a matter of design. In my view it is not a good idea to burden return values with having to transport error objects because, as the Outcome review showed, that creates a ton of competing goals and it is very difficult (perhaps impossible) to address all or even most of them without stripping the outcome<> type from all meaningful error-handling semantics, effectively turning it into variant<>.
``` outcome<T> append_Y(T t, Y y);
outcome<T> fun(X x, Y y) { outcome<T> t = make_X(x); return append_Y(t, y); // fails to compile
return append_Y(TRY(t), y); // ok, and safe } ```
With exceptions you would do:
T append_Y(T t, Y y); T fun(X x, Y y) { return append_Y(make_X(x),y); }
The "fails to compile" -- which in the case of Outcome serves the purpose of making sure that you don't forget to check for errors -- is gone because the compiler checks for errors for you. Literally, in this case Outcome protects you form a logic error that is impossible to make if you use exceptions.
Yes: exceptions are well designed. And the decision to cancel any operation that depends on the currently failed one -- by default -- is a reasonable one, and in fact desired. Also, the decision made by `outcome` -- when you are in the 3% area where explicit control paths are preferred -- to fail to compile by default is acceptable, and in fact desired. In contrast, the behavior of Boost.Noexcept is just to let the depending functions execute. This is my initial concern. In case of exceptions you can have both neutrality and a good default action upon throw. In case of Boost.Noexcept, in order to provide the neutrality, the default action is not that good anymore. Actually, how can you write an exception-neutral code with Boost.Nowide? Regards, &rzej;