
On Tue, Jan 30, 2018 at 4:40 PM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
The policy-based design is especially problematic because of the expressed desire to provide interoperability between diverse APIs each using different error reporting. This is a bit counter-intuitive, because the natural inclination is to provide maximum flexibility, but in this case other considerations are more important. There is a reason why in C libraries the default error reporting mechanism is to return int, even though the language does permit programmers to return structs, which would be more flexible.
As the last section of the tutorial covers, there is a non-source-intrusive mechanism for externally specifying interoperation rules to handle libraries using one policy interoperating with libraries with different policies. This lets Eve stitch together the Alice and Bob libraries without having to modify their source code, and without affecting any other libraries. I personally think this Outcome's coup de grace and why it's the only scalable choice for large programs considering using this sort of error handling.
This is wishful thinking. Imagine someone wanted to create a library that enabled all the different string types used in various libraries to get seamlessly stitched together. I'm sure that's possible, but I don't think it's a good idea. Ultimately, you pass char const * if you want to be compatible. Not ideal, not always possible, but that's how things are sometimes. It is the same with error handling: if you want to facilitate inteoperability, you'd return an int. Not ideal, but it's still the best option for interoperability. In addition, semantically policies don't make sense to me. For example, to throw or not to throw can not be a matter of "policy" because that makes throwing completely pointless.
[snip why Emil loves exceptions] So, the design of "an error handling library that does not use C++ exceptions" has to target the tricky bits where exceptions would be annoying or can't be used, not the general case where they can.
I get Emil that you love exceptions. So you won't like the whole premise behind Outcome, indeed you wrote an alternative to Outcome v1 last year to prove the pointlessness of the Outcome premise.
I wanted to be fair and refrained from mentioning (Boost) Noexcept but the reason why I wrote it was not to prove Outcome is pointless, but because I saw the point in Outcome, disagreed with the design, yet I had nothing tangible to prove even to myself that another approach is preferable (and there are problems where the best solution is not ideal.) It's not that I love exceptions, but that there is no other way to enforce postconditions and, like I already pointed out, postconditions are an integral part of the object encapsulation model in C++. Besides, there is *nothing* good about having to write: f1(); if( error ) return error; f2(); if( error ) return error; More importantly, there are *no* technical reasons why this should be slow when done automatically by the compiler. Literally, the people who insist on using OUTCOME_TRY instead of throwing exceptions have no reason to want that, except for lack of better optimizers (and even then, they have no evidence that the current state of affairs is "too slow"). I would very much like to be proven wrong about this, that there is *nothing* inherently slow in exception handling. It would save me a lot of energy. :) It is this part of Outcome, where it is trying to solve a problem much better solved by exception handling, that I find pointless, except in cases where exception handling is off limits, for example when errors need to pass through third-party code that is not exception-safe, in particular when errors need to be transported across C code. But Outcome does nothng to solve this problem. In addition, I don't find it pointless to postpone or avoid exceptions being thrown. Writing: optional<int> value; try { value = foo(); } catch( error & ) { } if( value ) use_value(value); is cumbersome. It is better to be able to say if( result<int> value=foo() ) use_value(value.get()); But this is not the "stitch together all the error handling object types in the universe" problem.
C-style callbacks. This would be nice to support because C++ exceptions are off-limits in this case, yet it is sometimes desirable to communicate user-specific information across the third-party C callback mechanism (this is sometimes supported by a void * user data pointer, but that is not always the case).
The kind of objects that can survive crossing API boundaries would be of basic types, or have one or two members of basic types. Think std::shared_ptr<T>, which _always_ consists of two pointers, rather than SmartPtr
which consist of who knows what. Note that Speaking of interoperability, it is especially tricky to report errors from this
is not the same problem as "using result<T> from C code", which Outcome allows. The important question is not how do I use result<T> from C, but how do I transport result<T> across a third party context which knows nothing about Outcome (as a side note, C++ exceptions also cause issues when crossing API boundaries, even if we set exception safety requirements aside.)
How do you transport any type across a third party context which knows nothing about that type?
There is a long list of standard techniques. Outcome is just another C++/C type, and any of those techniques apply just the same to it for that problem solution.
I don't see the issue you're making here.
Are you saying that you recommend returning shared_ptr
this in C++.
If you mean that many people hold unsubstantiated beliefs and want something, that's just life. :)
Finally, I'll point out that a lot of the positive feedback comes from people who think that it is a good idea to replicate the Rust error reporting mechanism in C++. This seems to be an axiomatic belief, since I've never seen anyone attempt to substantiate it. This is important, as it is typical for programmers coming from other languages to be shocked by various C++ language features, and this should not be confused with problems in the C++ language specification.
If this was being forced on all users across the board, I can see you might have a point.
But this is an opt in library, and nobody is claiming nor pretending that anything but a minority will ever want to use it. Using Outcome in your code does come with costs in terms of maintenance and learning curve. Most C++ users will never want nor need it.
Obviously, I'm not against people using Outcome, not that anyone would care what I think.
- What is your evaluation of the implementation?
Lack of C++11 support could be problematic. The use of macros to disambiguate namespace is cumbersome. Overall the library relies heavily on macros, which is not a good thing for a C++ library.
I have no idea where you get the idea that the library relies heavily on macros.
The only obligatory macro is BOOST_OUTCOME_V2_NAMESPACE. All the rest are optional.
And as I've explained several times now, the permuting namespace is to force DLLs built against version X of Outcome to interoperate with other DLLs built against Y of Outcome via the ValueOrError Concept interface.
I'd have thought Emil given your experience in games that you'd understand the importance of stable ABI guarantees. This makes stable ABI guarantees possible for the standalone Outcome.
How does that benefit Boost users? But you have a point that most macros are dealing with using Outcome from C, my bad.
Now, any Boost.Outcome would eventually decay its macro into boost::outcome::v2 once a final v2 has arrived. That's not a v2 design, that's a v2 *implementation*. I'll be running the ABI Compliance Checker per commit once v2 is decided upon to ensure it remains stable.
v2 of Boost Outcome or standalone Outcome?
It seems like Outcome wants to stay away from Boost. By that I mean that great care has been taken to decouple it from Boost, and I sense that this desire comes from the target audience: the so-called "low latency" crowd wouldn't touch Boost with a 9 foot pole. While it is generally a bad idea to speculate about such things, it seems to me the motivation for submitting Outcome for a Boost review is not to benefit the Boost community; for us the coupling with Boost is not a problem.
The decoupling is purely for ease of maintenance. I can script patchsets from a Boost.Outcome into Outcome, and vice versa. I am sorry that you have read ulterior motives into what is an engineering choice.
That's why I said it is a speculation. But this still leaves me wondering which one is the real Outcome and why are there two? Is the intention to deprecate standalone Outcome? Again, this is what makes stable ABI guarantees possible. I'm amazed
that you don't get this.
You're looking at the problem of ABI compatibility as "what do we have to do to get this complex type passed across a DLL boundary correctly." I'm looking at it as "how should we design this type so that ABIs don't break as much". Policies (much less user-defined policies) don't fit into this view. Emil