Boost logo

Boost :

Subject: Re: [boost] [outcome] Requesting second pre-review of Boost.Outcome tutorial
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-01-16 18:22:02


2017-01-16 16:50 GMT+01:00 Niall Douglas <s_sourceforge_at_[hidden]>:

> >> Many thanks in advance for any feedback received.
> >>
> > I intend to review the docs again. So far I had only a glimpse at the
> > initial page, and it really looks good. I was now able to grasp the
> > idea of the library in less than a minute.
>
> Cool. The simplest primitives are often the hardest to convey the use
> case for.
>
> > One thing I wanted to bring up immediately, is not really related to
> > your library but to a detail in the example. It uses `noexcept` to
> > illustrate that the function does not throw itself:
> >
> > ``` bo::outcome<int> getConfigParam(std::string name) noexcept; ```
> >
> > I strongly believe that this is the wrong thing to do (or at least
> > controversial) to annotate a function that can fail-but-not-throw as
> > noexcept. It is more in the spirit of noexcept intentions to
> > indicate a no-fail guarantee rather than the no-throw guarantee.
> >
> > I have provided the justification for this claim in the following
> > post: https://akrzemi1.wordpress.com/2014/04/24/noexcept-what-for/
> >
> > And conversely, only because you guarantee that a given function
> > never throws, it does not immediately imply that a function should
> > be declared as noexcept.
> >
> > I think that your example would not suffer if the noexcept is
> > removed. It still does the good job of illustrating the intent. But
> > you would avoid certain controversies that might divert the reader's
> > attention from the main point.
>
> Most of what you write in your blog I would generally agree with and you
> saved Outcome from a design mistake in
> https://akrzemi1.wordpress.com/2014/12/02/a-gotcha-with-optional/.
>
> But regarding
> https://akrzemi1.wordpress.com/2014/04/24/noexcept-what-for/ I think
> you're wide of the mark, and actually because I recognise that your
> opinion is not uncommon, it's one of the reasons why the tutorial bangs
> on about error handling in C++ in such lengthy detail plus presenting
> the error handling design patterns in does in such hand wavy and
> superlative language terms. Indeed I *am* trying to sell something, and
> it is the enormous value of making all your extern APIs noexcept which I
> call "sea of noexcept, islands of throwing" in the tutorial.
>
> Quoting your blog post:
>
> > Why do you need to know?
> >
> > Is this an important information for you if a given function may
> > throw or not? If so, why? Some possible answers include the
> > following:
> >
> > 1. Because if it throws and I do not catch it, std::terminate will
> > be called and I do not want this to happen.
> >
> > 2. Because I need this function to provide no-throw exception safety
> > guarantee.
> >
> > 3. Because this may allow certain compiler optimizations.
>
> I agree with you that answers 1 and 3 ought to be discounted for the
> large majority of C++ programmers. If you are using noexcept for those
> reasons, then don't because it's a lousy solution to not the problem you
> think you have. About answer 2 you say:
>
> > If your motivation is (2), noexcept will also not help you much.
> > Suppose you detect that the function is not declared as noexcept,
> > what do you do? Don’t use it? It may still not throw exceptions. A
> > function can throw nothing and still be declared noexcept(false).
> > This is the case for the std::swap specialization for STL
> > containers: following the recommendation from N3248 it is guaranteed
> > not to throw, but is declared noexcept(false). For this reason, the
> > example I gave in my other post with function nofail_swap is wrong.
> > It doesn’t take into account that swap on STL containers is no-fail.
> > You cannot check the no-fail or no-throw guarantee with a
> > compile-time expression, because in some cases it is just announced
> > informally in the documentation.
> >
> > Also, if a function is declared noexcept it can’t throw, but it can
> > call std::terminate. Do you consider this behaviour suitable for a
> > component that is supposed to be “exception safe”?
>
> Here is where I think you've missed the beat. In everything you say
> above you are 100% correct. noexcept has a lousy, poorly thought through
> implementation in C++ and it shows every bit of having been tacked on at
> the last minute in the C++ 11 standard, and I said so at the time to
> anyone who would listen in 2010. But do you see that the lousy
> implementation has nothing to do with this:
>
> > 2. Because I need this function to provide no-throw exception safety
> > guarantee.
>
> Your argument against using noexcept to make a function provide a
> no-throw exception safety guarantee was all about the lousy
> implementation of noexcept, and nothing about whether explicitly
> guaranteeing that calling some function will not invert control flow is
> a good or bad thing.
>
> You are absolutely right that marking a function with noexcept means it
> simply adds a call to std::terminate which is usually not what the
> programmer intended. That's that lousy implementation again, not least
> that this stupid piece of code:
>
> void somehow_this_is_not_a_compile_failure() noexcept
> {
> throw "foo"; // i.e. std::terminate()
> }
>
> ... is legal, and worse, many compilers don't even warn, you need to run
> clang-tidy on it to get any indication of this being very unlikely to be
> what the programmer intended. But that's a problem for the library
> implementor, and a very different viewpoint arrives at the library *user*.
>
>
> One of the main reasons you'd want to use Outcome is because *you really
> don't want control flow to invert most of the time*. There is a lot of
> buy in for this amongst SG14 members and others with really big C++
> codebases, but for STL maintainers and Boost devs it is not as widely
> appreciated how much more costly debugging and maintaining multi
> translation unit code which can invert control flow is. That said, there
> is recognition even amongst the hardcore of SG14 members that being able
> to use the standard STL rather than the EA custom STL more frequently
> would be useful, so if one could create small, localised islands where
> exception throws can happen just within that translation-unit-local
> island then one could use the STL just within that island. The island is
> then guarded by a catch all try catch because you can't safely have
> anything else in a noexcept function. This leads to the "sea of
> noexcept" design pattern described in the Outcome tutorial which is:
>
> extern outcome<void> some_public_api() noexcept;
> ...
> outcome<void> some_public_api() noexcept
> {
> try
> {
> ... STL using code ...
> return {}; // return empty outcome
> }
> catch(...)
> {
> // return exceptioned outcome, defaults to using
> std::current_exception()
> return make_exceptional_outcome<>();
> }
> }
>
> All the above requires some programmer discipline, but I would argue
> much less programmer discipline than writing exception safe code which
> is correct and bugfree.
>
> Also, the tooling will catch up. clang-tidy is getting better at warning
> you when you forget the catch all try catch wrapping a noexcept
> function. I also intend, at some point, to add Outcome-awareness to
> clang-tidy so you'll get a much harder error if you leak exceptions out
> of an outcome returning noexcept function. Or, in other words, by
> returning outcomes the programmer really does not intend exception
> throws to be aliased into std::terminate() for them by the compiler.
>
> Finally back this statement of yours:
>
> > I strongly believe that this is the wrong thing to do (or at least
> > controversial) to annotate a function that can fail-but-not-throw as
> > noexcept. It is more in the spirit of noexcept intentions to
> > indicate a no-fail guarantee rather than the no-throw guarantee.
>
> On this I think you're just plain wrong. noexcept has a very specific
> meaning on an API for the *users* of that API: calling this API will not
> invert control flow. Not EVER. And you can write code calling it
> assuming that in the strongest sense possible. It does NOT mean that the
> function cannot fail.
>
> The reason why is the heritage from C. Any C function does not throw
> exceptions, yet they definitely can fail. I would say most C++
> programmers would agree therefore that marking a function with noexcept
> means "no exceptions", not "no failures".
>
>
> So, tl;dr and all that, I agree with your opinion if I'm wearing my
> library developer's hat. I disagree with you if I'm wearing my library
> user's hat. Hopefully all that above actually made some sense.
>

I am confused about the usage of term "inversion of control" in the context
of throwing exceptions. Maybe I am missing something obvious; but what do
you mean when you say that "calling noexcept never causes an unexpected
inversion of control"?

Regards,
&rzej;


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