Boost logo

Boost :

Subject: Re: [boost] [review] Review of Outcome v2 (Fri-19-Jan to Sun-28-Jan, 2018)
From: Vinícius dos Santos Oliveira (vini.ipsmaker_at_[hidden])
Date: 2018-01-28 21:27:40

2018-01-28 1:02 GMT-03:00 Emil Dotchevski via Boost <boost_at_[hidden]>:

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

The hidden throw.

int a = 4;

int x() {
  a = y();
  return z(a);

Any C programmer will think this function is as clear as it can be. Add a
C++ programmer in the game and he'll start to think about possible
exception interactions and maintain a useless mind state in his head which
solves no problems. Lots of languages do fine without exceptions. And Rust,
which is very similar to C++ (only pay for what you use, systems
programming language, RAII...) also does fine without exceptions. You do
have less complication.

Go write a container and then you don't know whether functions will throw.
Now you have to maintain two lines of control flow in your head. It won't
be fun at all.

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:
> /24/questions-about-
> exception-specifications/
> <>
> .

This post will teach what is the proper way to "catch everything" in
response to "how can I be sure I'm catching all exceptions?". However, one
of the points is to not introduce new error types ever.

Why would a user who is calling parse_int handle errors other than "invalid
data" and "overflow error"? What should the developer put in the catch-all
statement? Sometimes, a catch-all statement won't make sense.

The problem here — really — is a brittle architecture in large code bases
to handle error cases (which should happen only occasionally and most
likely won't be tested as much as the success case). Add a new type failure
that isn't expected by the callers and you break everything. If the user
doesn't have access to the sources of an updated library, he won't even be
able to test if his calls need to be updated. Now, add this to the fact
that exception specification is a bad-practice/not-really-used in C++ and
you may agree that this architecture is brittle.

If I refactor large code bases in Rust projects, I'll never face similar
problems. Everything will be compile time errors without any special setup
that only advanced users could pay.

This post you link here will also explain why exception specifications
isn't a good idea. The argument is twofold:

   - “no one really knows how to design them”.
   - They are broken.

What does this have to do with Outcome? This is not the topic being
discussed here. The conclusion to this question is irrelevant here (a.k.a.
ignoratio elenchi).

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.

C++ didn't have Expected or Outcome's result back then. This is why
exceptions are the only way to report an error from destructors.

But a constructor is just a function which will instantiate an object of
the given class. The only "special" requirement for such function is ACL to
access all class' members. You have this idiom in C++ (private

Whether you choose to design your objects using exceptions or
Boost.Outcome, your functions /can/ rely on invariants of the classes.

So, nothing special about exceptions here except for legacy code.

Conclusion: exceptions are only an integral part of RAII in C++. Rust does
have RAII and it doesn't need exceptions.

If the objects you are instantiating use Boost.Outcome instead exceptions,
then there is no reason why you need exceptions to orchestrate the "object
fully constructed" scenario. Moves will never throw. You can just construct
the object parts (potentially returning early in case of failure) and them
moving everything into a structure of the proper class.

Maybe I'm simplifying too much. Tell me if that is the case.

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.

Given your previous argument was attacked, you can no longer follow/derive
this argument.

Therefore, it is not this much manual system as you imply.

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. :)


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

Write `return open_file(path).and_then(read_data).and_then(parse);` then.

It'll compile and work. Much simpler than to design a container which needs
to take consideration the hidden control flow of exceptions everywhere. How
much effort did we lose into this already? There was one of the review
comments here about Niall getting the swap exception specification wrong,
for instance. Is it every day and every week? And errors popping without we
noticing? What is the argument to have such brittle error system (exactly
the thing which should be the most robust)?

Vinícius dos Santos Oliveira

Boost list run by bdawes at, gregod at, cpdaniel at, john at