Boost logo

Boost :

Subject: Re: [boost] [review] Review of Outcome v2 (Fri-19-Jan to Sun-28-Jan, 2018)
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2018-02-04 00:53:28


> std::error code addresses your concern about potential translations
> of exceptions. It does not address your concern about communicating
> arbitrary data. But in the end it might turn out to be a reasonable
> trade-off for low-level libraries that disable exceptions.

I entirely agree. There is a tradeoff here, a balance. I've always been
of the opinion that if you want to return arbitrary data, either you
encode it into the type system and thus increase coupling, or you type
erase it usually using malloc. Exception throws are a very special case
of this because the C++ runtime does the type erasure for you.

Emil you have pointed out many times that the C++ compiler ought to
optimise a try { throw x; } catch(x) { ...} by eliding the
throw-try-catch in runtime. And I agree, but none of the major compilers
do do that, and for a long list of good legacy reasons. Thus in terms of
compiler technology available here and now and today - not a maybe
tomorrow - there are plenty of use cases where taking on more
inconvenience and ugliness in some parts of your code can be beneficial.

> However it looks like Outcome provides a solution for most of the practical
> cases, while leaving the general case unsolved. Boost.Outcome is a set of
> tools (rather than just one) and you are expected to choose one that best
> solves your particular problem.

I was just about to say the same thing, but more pointed. Exception
specifications were a bad idea because of the potential presence of
unknown code (despite what Peter says, I do and did not find exception
specifications to be useless in theory). That makes them a bad idea in
*general* i.e. as a rule of thumb. But that says nothing about the local
use case where no unknown code may operate, and all code potentially
executable is known both to the programmer and the compiler. In that
situation, I do believe that in a localised use case, exception
specifications can add significant value. Knowing that a piece of code
will never, ever see stack unwinding lets you skip handling stack
unwinding. Hence `noexcept`.

Furthermore, unlike with exception specifications which were unhelpfully
checked at runtime, Outcome fails at compile time. That's a very
different kettle of fish. You can't successfully compile code if your E
types don't have interop specified for them.

Again, Herb's article is right that statically checked exception
specifications are a bad idea in *general*. But they can be very useful
*locally*. Indeed, that's the whole point of the Expected proposal, and
WG21 has greenlit that one. It's coming to future C++ whether you like
it or not (bar some major surprise).

> Remember that the goals you list and arguments that Herb Sutter draws apply
> to a general failure object transportation mechanism present everywhere in
> the program, in any program. In contrast, Outcome is not intended to be a
> failure reporting mechanism in the entire program: it is either to be used
> in isolated places (with particular conditions), or in programs with
> extremely harsh execution constraints, where many inconveniences are
> expected, including the inability to freely transport arbitrary amount of
> failure information in arbitrary form.

Absolutely agree. I always envisaged Outcome being useful only within a
low level layer of code close to the bare metal. Code where a unknown
potential ten thousand CPU cycles might matter. Anybody who doesn't care
about that kind of stuff doesn't have much need for Outcome.

> So, case 1. You are using in your program a boost::filesystem2 library ("2"
> because we hypothetically assume it uses Boost.Outcome to report failures).
> Your program can freely use exceptions. But often the inability to write to
> the file is not something you have to propagate up, but you know how to
> handle it locally. This is not a generic context. I exactly know what
> library I am using, and one level up there will be no `result<>`, there
> will only be exceptions. In this case the most proper tool from Outcome
> toolbox is to create your own type representing the failure code and two
> file names, and the usage of `result<>` with your type: `result<T,
> FilesystemFailureType>`. The question "how this interacts with `result<T,
> SomeOtherType>`" is irrelevant, because there will never be such
> interaction.

Completely agree.

And much of the additional "unnecessary" complexity in Outcome is to
handle what happens when a programmer is faced with two third party
libraries using Outcome/Expected with incommensurate types. Implementing
a non-source-code-intrusive mechanism to let the programmer inject what
to do comes with messy complexity, some of which Rob pointed out in his
review.

I don't expect that situation to emerge at all in most code for at least
five years. But as a userbase grows, especially after Expected enters
C++, then that situation will emerge frequently. And at that point
Outcome has them covered (and unlike Expected or anything proposed
currently to WG21 I might add).

> There are probably more cases, but my point is:
>
> 1. Outcome is not meant to be a full failure-handling framework for every
> part of every program (even though it is technically possible to use it
> this way).

Absolutely agreed. I felt it was important that it is technically
possible to completely replace exceptions with Outcome throughout a
large codebase. I would recommend anybody to not do that though.

> 2. It addresses the specific cases through specific trade-offs where it
> does not have to address all the issues of the full failure handling
> framework.

Also absolutely agreed.

Niall

-- 
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

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