History

Outcome has had an interesting history, and it is worth summarising it here to show how a Boost library comes to life. The following recollections are by Niall Douglas, and may be faulty due to his aging memory.

gantt dateFormat YYYY-MM title History of the Outcome library Outcome v1: done, des1, 2014-06,2017-05 Boost peer review of v1: done, des2, after des1, 30d Outcome v2 development: done, des3, after des2, 2018-01 Boost peer review of v2: done, des4, after des3, 30d Outcome v2.1 development: active, des5, 2018-03, 2018-12 section Events Separated from AFIO v1: crit, done, 2014-06,2d Boost.Expected added: crit, done, 2014-10,2d Boost.Expected replaced with feature complete basic_monad: crit, done, 2015-08,2d Non-allocating future-promise implementation dropped: crit, done, 2016-05,2d C++ 11 support dropped: crit, done, 2016-06,2d Implemented LEWG expected<T, E> using basic_monad: crit, done, 2017-02,2d Outcome v1 replaced with prototype v2 in git repo: crit, done, 2017-07,2d Boost.Outcome generated by script from Outcome repo: crit, done, 2017-10,2d New tutorial finally complete: crit, done, 2017-12,2d Planned release of v2.1 into Boost 1.69: crit, active, 2018-12,2d

The genesis of Outcome v1

The git repo began life as a “Boost.Spinlock” in June 2014 hived out of Boost.AFIO v1 where it had existed for some time as an internal library. In October 2014 I added in the original prototype Boost.Expected reference library as a git submodule, and began developing a non-allocating future<T>/promise<T> as an extension of expected<T, std::exception_ptr> as a faster, monadic future-promise was something which AFIO v1 sorely needed.

The original prototype Boost.Expected library was a large and very complex beastie. I was fortunate to be employed on a contract in late 2014 early 2015 where I saw it deployed at scale into an existing large C++ codebase. Expected was really great and powerful, but it absolutely murdered compile times in a large C++ codebase, and made LTO effectively infeasible. I also found its implementation non-conducive to implementing future-promise with it, and so I resolved to implement a much more powerful policy driven monad factory which could stamp out everything from an option<T> right through to a future-promise pair, all using the exact same basic_monad<> and therefore all with a full monadic programming API, C++ 17 continuations/monadic bind and intelligently convertible into one another. Moreover, all this needed to have an absolute minimum impact on compile times and runtime overheads, neither of which were strengths of the original prototype Boost.Expected library.

By August 2015 “Boost.Monad” was delivering on all those requirements and then some, but it lacked maturity through use in other code. Summer 2015 saw the Boost peer review of AFIO v1 which was roundly rejected. After considering the ample review feedback, it was realised that AFIO v2 would be a very different design, one no longer using futures, memory allocation nor C++ exceptions. As AFIO v2 was started from scratch and using Outcome heavily from the very beginning (every AFIO v2 API returns a result<T>), Outcome began to gain bug fixes and shed features, with the non-allocating future-promise implementation being dropped in May 2016 and a large chunk of metaprogramming being replaced with cleaner variable templates in June. After CppCon 2016 in September, then began the long process of getting Outcome ready for Boost peer review in Q1 2017 which involved a repeated sequence of complete rewrites of the tutorial in response to multiple rounds of feedback from the C++ community, with at least four complete rewrites currently at the time of writing.

In parallel to all this development on Outcome, Expected went before the LEWG and entered the C++ standards track. As the WG21 meetings went by, Expected experienced a period of being stripped back and much of the complexity which had so murdered compile and link times in 2014-2015 fell away, thus the Expected proposed in P0323R1 ended up landing so close to Outcome that in January 2017 it was just a few hours work to implement Expected using the core basic_monad infrastructure in Outcome. That highly flexible policy based design which made monadic future-promise possible made it similarly easy to implement a highly conforming Expected, indeed in early 2017 Outcome’s Expected was much closer to P0323R1 than any other implementation including the LEWG reference implementation. And unlike the LEWG reference implementation, Outcome has had eighteen months of that finely tuned patina you only get when a library is in use by other code bases.

In February 2017 it became realised that the userbase really wanted a high quality expected<T, E> implementation rather than anything similar but not the same which Outcome had invented. The only just implemented Expected implementation based on basic_monad therefore took primacy. The final rewrite of the documentation before peer review submission was one which made it look like Outcome was primarily an expected<T, E> implementation with a few useful extensions like outcome<T> and result<T>. I was sad to so pivot, but it was obvious that Outcome would see far wider popularity and usage as primarily an Expected implementation.

Almost three years after its beginning, Outcome v1 finally went before Boost peer review in May 2017 which turned into one of the longest and most detailed peer reviews Boost has done in recent years, with over 800 pieces of review feedback submitted. It was by consensus rejected, with substantial feedback on what to do instead.

Outcome v2

During the very lengthy peer review, roughly three groups of opinion emerged as to what a value|error transporting class ought to look like:

1. Lightweight
A simple-as-possible T and/or E transport without any implementation complexity.
2. Medium
A variant stored T or E1 ... En where T is the expected value and E1 ... are the potential unexpected values. This implemention really ought to be implemented using C++ 17's std::variant<...> except with stronger never-empty guarantees.
3. Heavy
A full fat Either monad participating fully in a wider monadic programming framework for C++.

Peter Dimov was very quickly able to implement an expected<T, E1, ...> using his variant2 library, and thus there seemed little point in replicating his work in an Outcome v2. The lightweight choice seemed to be the best path forwards, so in June 2017 the bare minimum result<T, E> and outcome<T, EC, P> as presented in this library was built, using the same constructor design as std::variant<...>. Significant backwards compatibility with v1 Outcome code was retained, as the review had felt the basic proposed design fine.

A period of maturation then followed by porting a large existing codebase using Outcome v1 to v2, and writing a significant amount of new code using v2 to test it for unanticipated surprises and bugs. Quite a few corner cases were found and fixed. At the end of September 2017, Outcome v2 was deemed to be “mature”, and a script generated “Boost edition” made available.

All that remained before it was ready for a second Boost peer review was the documentation. This took four months to write (same time as to write the library itself!), and in January 2018 Outcome had its second Boost peer review, which it passed!

Post-review

Outcome passing its review in January had much more consequence than I could have ever expected. Unbeknownst to me, the WG21 leadership had interpreted the success of Outcome, and especially its divergences from WG21 Expected, as a sign that the C++ exception handling mechanism was no longer fit for purpose. It was thus resolved to remedy the standard exception handling mechanism into something much more efficient, thus rendering Outcome obsolete in future C++ standards.

Just before the review, I had mooted a number of semantic and compile time performance improvements to <system_error> with the proposal that we mildly break Boost.System with improvements and see how badly real world code broke in response. This was not widely accepted. I therefore wrote an improved <system_error2> which fixed all the problems listed at P0824 (Summary of SG14 discussion on <system_error>) and fixed up Outcome so one could use it without any system error implementation, or with the STL one or with the proposed improved one.

To be continued after Jacksonville …