Boost logo

Boost :

Subject: Re: [boost] [review] Review of Outcome v2 (Fri-19-Jan to Sun-28-Jan, 2018)
From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2018-01-28 04:02:59


On Sat, Jan 27, 2018 at 5:33 AM, Vinícius dos Santos Oliveira <
vini.ipsmaker_at_[hidden]> wrote:

> 2018-01-27 0:31 GMT-03:00 Emil Dotchevski via Boost <boost_at_[hidden]
> >:
>
>> Question: if using the OUTCOME_TRY macro is equivalent to calling the
>> function, checking for error and then returning an error if there is an
>> error, how is this different from using exceptions? Semantically,
>> exception
>> handling does nothing more than check for errors and returning errors if
>> there were errors
>
>
> There is a single control flow to analyse: the return of the function. You
> don't need a "parallel" control flow construct to check for error case.
>

Where is the parallel control flow in return parse(read_data(open_file()))?

>
> - You can't forget to check the error case (it's part of the type
> system).
>
> You can't forget to check for errors if you use exceptions, either.
Literally, if you use exceptions it is as if the compiler writes the ifs
for you.

>
> - It's self-documenting.
>
> Only to the extent that you can see that a function may return an error.
With exceptions, except for noexcept functions, functions may "return" an
error.

Some would count the fact that with e.g. Outcome you can specify what kind
of errors can be returned as an advantage, but that is similar to
statically enforced exception specifications. Sutter explained why that is
a bad idea back in 2007: https://herbsutter.com/2007/01/24/questions-about-
exception-specifications/.

>
> - There are no strange interactions between Outcome and the rest of
> the language (e.g. throwing destructors, transporting exception between
> threads, and so on...).
>
> So, don't throw in destructors. Also, you can't use Outcome in
destructors, but that is fine -- it is a logic error to not be able to
destroy an object.

Though this reminds me: in C++, exceptions are the only way constructors
may report an error, and this is very deliberate, integral part of RAII.
This guarantees that you can't use an object that failed to initialize,
which is the reason why member functions are free to assume, rather than
check, that all invariants of the class have been established.

Thus, exception handling is an integral part of the C++ object
encapsulation model. Choose to not use exceptions and the result is that
like in C, each function must check whether the object was initialized, and
return some error code to indicate that condition. You're replacing a
bullet-proof automatically enforced error checking system with a manual
one, prone to errors; worse, we're talking about error handling code, which
by its very nature is difficult to test.

OUTCOME_TRY is just convenience. It mirrors the Rust's try macro:
> https://doc.rust-lang.org/1.9.0/std/macro.try!.html
>

There are many languages which lack C++ exception handling, it doesn't mean
that their approach is better. It is common for programmers coming from a
different background, forced by reality to have to use C++, to complain
what a horrible language it is for lacking this or that feature. :)

> With monadic operations, we could turn the above code into something like:
>
> return open_file(path).and_then(read_data).and_then(parse);
>

> But this assumes all operations return the same error type (e.g.
> std::error_code).
>

I'd much rather use return parse(read_data(open_file())) without having to
assume anything.
Emil


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk