Boost logo

Boost :

Subject: Re: [boost] Outcome v2
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-07-13 09:16:48


2017-07-12 20:49 GMT+02:00 Emil Dotchevski via Boost <boost_at_[hidden]>
:

> On Wed, Jul 12, 2017 at 10:23 AM, Andrzej Krzemienski via Boost <
> boost_at_[hidden]> wrote:
>
> > > On Tue, Jul 11, 2017 at 3:16 PM, Emil Dotchevski <
> > emildotchevski_at_[hidden]
> > > >
> > > wrote:
> > >
> > > > On Tue, Jul 11, 2017 at 2:11 PM, Niall Douglas via Boost <
> > > > boost_at_[hidden]> wrote:
> > > >
> > > >> > Outcome does not solve the most important problem that needs
> solving
> > > by
> > > >> an
> > > >> > error handling library.
> > > >>
> > > >> Does Expected?
> > > >>
> > > >
> > > > Nope, but the original outcome<T> was closer. I thought, perhaps it
> is
> > > > possible to make it able to transport arbitrary error types, and not
> by
> > > > exception_ptr. My attempt at solving that problem lead me to TLS but
> > > maybe
> > > > there are other ways.
> > > >
> > >
> > > Allow me to clarify. Suppose I have a function foo which returns FILE *
> > on
> > > success, some EC1 on failure, and another function bar(), which calls
> foo
> > > and returns an int on success, some EC2 on failure. I believe in terms
> of
> > > Outcome this would be:
> > >
> > > outcome::result<FILE *,EC1> foo() noexcept;
> > >
> > > outcome::result<int,EC2> bar() noexcept {
> > > if( auto r=foo() ) {
> > > //no error, use r.value(), produce the int result or return EC2(x).
> > > } else {
> > > return ______;
> > > }
> > > }
> > >
> > > What do you recommend in place of _____?
> > >
> > > Here is the same code in terms of Noexcept:
> > >
> > > FILE * foo() noexcept;
> > >
> > > int bar() noexcept {
> > > if( FILE * r=foo() ) {
> > > //no error, use r, produce the int result or return throw_(EC2(x)).
> > > } else {
> > > return throw_();
> > > }
> > > }
> > >
> > > That is, with Noexcept, bar() would not care what error types propagate
> > out
> > > of foo because it can't handle any errors anyway. Whatever the error
> is,
> > it
> > > is simply passed to the caller.
> >
> > The default EC type in outcome::result<> is std::error_code, and it is
> > possible to use only this type throughout entire program
>
>
> I didn't mean that you would design the program like this, the point is
> that you may not have control over the fact that foo returns EC1, and you
> still have to be able to write bar() (I should have made that clearer by
> using different namespaces.)
>

So, you are saying that one can construct a programming problem that
Outcome library will not be able to solve. I agree. If you are forced to
return EC1 and are forced to use something that returns EC2, where EC1 is
not related to EC2, you will probably not be able to solve it decently.

>
> In this comment you're echoing what Niall was saying in the documentation
> of the original Outcome, where he had several paragraphs attempting to
> convince the reader to stop using random error codes and _always_ use
> std::error_code, with technical phrases like "don't be anti-social" :)
>

Yes. It is my understanding that the value of this library can be only
appreciated and exploited when you decide you will be using std::error_code
as EC uniformly (or otherwise apply other precautions). One can compare it
to the expectation that, C++ exception handling mechanism is useful
provided that you decide to throw objects that inherit directly or not from
`std::exception`. People generally don't have problem with this constraint.
And occasionally some clever people find reasons to throw other types, like
`boost::interrupted`.

>
> But that is not our reality, and it will never be our reality in C++
> because C++ programs do use C libraries,

I am not sure what using C libraries changes here?

> and because different C++
> libraries commonly use their own types for similar things. This is
> especially true for error codes.
>

I think this is what std::error_code was design for: to convey the original
(unconverted) numeric value of any error code (along with the error domain)
from any library in a uniform way.

The fact that result<T, EC> allows you to put just any EC should not be
understood that it is reasonable to return different EC1 and EC2 wherever
you like. I would intuitively expect of the users of `result<>` that they
use different EC's only in the following situations:

1. EC1 is std::error_code and EC2 is an opaque typedef on std::error_code,
and I need an opaque typedef to customize the behavior of `result` on it.

2. The usage of a custom EC2 is completely encapsulated from the outside
world: I use it in one translation unit internally, and never expose it in
the interface, as shown in my example with the parser:
https://github.com/akrzemi1/__sandbox__/blob/master/outcome_practical_example.md

> > given that these
> > errors are to be handled immediatly in the next layer, it should not be
> > that much of the problem.
> >
>
> In my example the error is not to be handled immediately in the next layer.
> I don't need an error handling library if the error is always handled "in
> the next layer".
>

If by "error handling" you mean "never be forced to respond to it in the
next layer" (note: not "handle" but "respond"), you may say that Outcome is
not a library for error handling. But you may still find it useful for
situations that are not "errors" by your definition, but that occur in
programs, and are "irregular" in some sense, and you need to respond to
them in the next layer.

To "respond" is different than to "handle", because the "response" is
usually to report it one level up, so that it still needs to be responded
to. It is tat this decision to forward the condition up is noted explicitly
in the code. Sometimes you do not want this explicitness and you will use
stack unwinding. Sometimes you want this explicitness and this is where
Outcome is helpful.

Regards,
&rzej;


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