folly-clib-20250713.1537: folly/folly/result/result.h
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/ExceptionWrapper.h>
#include <folly/Expected.h>
#include <folly/OperationCancelled.h>
#include <folly/lang/Align.h> // for `hardware_constructive_interference_size`
#include <folly/lang/RValueReferenceWrapper.h>
#include <folly/portability/GTestProd.h>
/// Read the full docs in `result.md`!
///
/// `result<T>` resembles `std::variant<T, std::exception_ptr, stopped_result>`,
/// but is cheaper, and targets "never empty" semantics in C++23. Its intended
/// use-case is a "better `Try<T>`", both in sync & `folly::coro` code:
///
/// - No "empty state" wart -- all state of `result` have a clear meaning,
/// as far as control flow is concerned.
///
/// - Easy exception checks:
/// if (auto* ex = folly::get_exception<Ex>(res)) { /*...*/ }
///
/// - User-friendly constructors & conversions -- you can write
/// `result<T>`-returning functions as-if they returned `T`, while returning
/// returning `non_value_result{YourException{...}}` on error.
///
/// - Can store & and && references. Think of them as syntax sugar for
/// `std::reference_wrapper` and `folly::rvalue_reference_wrapper`.
///
/// struct FancyIntMap {
/// int n;
/// result<int&> at(int i) {
/// if (n + i == 42) { return std::ref(n); }
/// return non_value_result{std::out_of_range{"FancyIntMap"}};
/// }
/// };
/// FancyIntMap m{.n = 12};
/// int& n1 = co_await m.at(30); // points at 12
/// result<int&> rn2 = m.at(20); // has error
/// co_return n1 + co_await std::move(rn2); // propagates error
///
/// Key things to remember:
/// - `result<V&&>` is "use-once" -- and it must be r-value qualified to
/// access the reference inside.
/// - `const result<V&>` gives non-`const` access to `V&`, just as `const
/// result<V*>` would.
///
/// - `has_stopped()` & `stopped_result` to nudge `folly` to the C++26 idea
/// that cancellation is NOT an error, see https://wg21.link/P1677 & P2300.
///
/// - Easy short-circuiting of "error" / "stopped" status to the caller:
/// * In `folly::coro` coroutines:
/// - `co_await co_await_result(x())` makes `result<X>`, does not throw.
/// - `co_await co_ready(syncResultFn())` extracts `T` from a
/// `result<T>`, or propagates error/stopped.
/// - `co_yield co_result(std::move(res))` returns a `result<T>`.
/// * In synchronous `result<T>` coroutines,
/// - `co_await std::move(res)` and `folly::copy(res)` give you `T`,
/// - `co_await std::ref(res)` gives you `T&` -- ditto for `std::cref`
/// and `folly::cref`.
/// * While you should strongly prefer to write `result<T>` coroutines,
/// propagation in non-coroutine `result<T>` functions is also easy:
/// if (!res.has_value()) {
/// return std::move(res).non_value();
/// }
///
/// - `result` is mainly used for return values -- implying single ownership.
/// For this reason, it encourages moves over copies (with a few carve-outs
/// for better usability), which also helps prevent perf bugs.
///
/// Note: Unlike `Try`, `non_value_result` (and thus `result<T>` in a non-value
/// state) will `std::terminate` in debug builds if you attempt to construct it,
/// or access it while it contains either of:
///
/// - `OperationCancelled` -- the header explains why user code should not
/// use that exception. Instead, store `stopped_result`, and use
/// `has_stopped()` to check for its presence.
///
/// - An empty `std::exception_ptr`. For prior art, consider that
/// `exception_wrapper::throw_exception` unconditionally calls
/// `std::terminate` when the wrapper is empty. By explicitly specifying
/// this as out-of-contract, and validating eagerly, we reserve this
/// representation to potentially mean something else in the future.
#if FOLLY_HAS_RESULT
namespace folly {
struct OperationCancelled;
namespace detail {
// In order to give `result` a stronger contract, debug builds prevent `result`
// and `non_value_result` from ingesting empty `std::exception_ptr`s, and ones
// with `OperationCancelled`.
//
// In prod, neither check is done since the legacy behaviors are "okay"-ish:
// - Empty `std::exception_ptr`s, while nonsensical in the context of
// `result`, are safe to use unless you call `throw_exception()`. And,
// unfortunately, `co_yield co_error(exception_wrapper{})` compiles.
// - As of 2025, erroring with `OperationCancelled` is the implementation of
// `co_yield co_canceled`, and some code paths actually rely on this, often
// erroneously (see `coro/Retry.h`). So, even as we work to reduce
// reliance on this in anticipation of C++26 "stopped" semantics, for
// the foreseeable future it will "sort of work".
void fatal_if_exception_wrapper_invalid(const exception_wrapper&);
inline void dfatal_if_exception_wrapper_invalid(const exception_wrapper& ew) {
// This code path could be hot in production code, so there's no branch or
// logging in opt builds.
if constexpr (kIsDebug) {
fatal_if_exception_wrapper_invalid(ew);
}
}
} // namespace detail
// Place this into `result` or `non_value_result` to signal that a work-tree
// was stopped (aka cancelled). You can also `co_await stopped_result` from
// `result` coroutines.
struct stopped_result_t {};
inline constexpr stopped_result_t stopped_result;
// NB: Copying `non_value_result` is ~25ns due to `std::exception_ptr` atomics.
// Unlike `result`, it is implicitly copyable, because:
// - Common usage involves only rvalues, so the risk of perf bugs is low.
// - `folly::Expected` assumes that the error type is copyable, and it's
// too convenient an implementation not to use.
class non_value_result {
private:
exception_wrapper ew_;
non_value_result(std::in_place_t, exception_wrapper ew)
: ew_(std::move(ew)) {}
template <typename Ex, typename EW>
static Ex* get_exception_impl(EW& ew) {
return folly::get_exception<Ex>(ew);
}
public:
/// Future: Fine to make implicit if a good use-case arises.
explicit non_value_result(stopped_result_t)
: ew_(make_exception_wrapper<OperationCancelled>()) {}
non_value_result& operator=(stopped_result_t) {
ew_ = make_exception_wrapper<OperationCancelled>();
return *this;
}
/// Use this ctor to report errors from `result` coroutines & functions:
/// co_await non_value_result{YourError{...}};
///
/// Design note: We do NOT want most users to construct `non_value_result`
/// from type-erased `std::exception_ptr` or `folly::exception_wrapper`,
/// because that would block RTTI-avoidance optimizations for `result` code.
explicit non_value_result(std::derived_from<std::exception> auto ex)
: ew_(std::in_place, std::move(ex)) {
static_assert(
!std::is_same_v<decltype(ex), OperationCancelled>,
// The reasons for this are discussed in `folly/OperationCancelled.h`.
"Do not use `OperationCancelled` in new user code. Instead, construct "
"your `result` or `non_value_result` via `stopped_result`");
}
bool has_stopped() const { return ew_.get_exception<OperationCancelled>(); }
// Implement the `folly::get_exception<Ex>(res)` protocol
template <typename Ex>
const Ex* get_exception(get_exception_tag_t) const noexcept {
static_assert( // Note: `OperationCancelled` is final
!std::is_same_v<const OperationCancelled, const Ex>,
"Test results for cancellation via `has_stopped()`");
return folly::get_exception<Ex>(ew_);
}
template <typename Ex>
Ex* get_mutable_exception(get_exception_tag_t) noexcept {
static_assert( // Note: `OperationCancelled` is final
!std::is_same_v<const OperationCancelled, const Ex>,
"Test results for cancellation via `has_stopped()`");
return folly::get_mutable_exception<Ex>(ew_);
}
// AVOID. Throw-catch costs upwards of 1usec.
[[noreturn]] exception_wrapper throw_exception() const {
detail::dfatal_if_exception_wrapper_invalid(ew_);
ew_.throw_exception();
}
/// AVOID. Use `non_value_result(YourException{...})` if at all possible.
/// Add a `std::in_place_type_t<Ex>` constructor if needed.
///
/// Provided for compatibility with existing `exception_wrapper` code. It
/// has several downsides for `result`-first code:
/// - It is a debug-fatal invariant violation to pass in an
/// `exception_wrapper` that is empty or has `OperationCancelled`.
/// See the `dfatal_if_exception_wrapper_invalid` doc.
/// - Not knowing the static exception type blocks optimizations that can
/// otherwise help avoid RTTI on error paths.
static non_value_result from_exception_wrapper(exception_wrapper ew) {
detail::dfatal_if_exception_wrapper_invalid(ew);
return non_value_result{std::in_place, std::move(ew)};
}
/// AVOID. Use `folly::get_exception<Ex>(r)` to check for specific exceptions.
/// It may be OK to add more specific accessors to `non_value_result`, see
/// `throw_exception()` for an example.
///
/// INVARIANT: Ensure `!has_stopped()`, or you will see a debug-fatal.
///
/// See `from_exception_wrapper` for the downsides and the rationale.
exception_wrapper to_exception_wrapper() && {
detail::dfatal_if_exception_wrapper_invalid(ew_);
return std::move(ew_);
}
friend inline bool operator==(
const non_value_result& lhs, const non_value_result& rhs) {
return lhs.ew_ == rhs.ew_;
}
// DO NOT USE these "legacy" functions outside of `folly` internals. Instead:
// - `non_value_result(YourException{...})` whenever you statically know
// the exception type (feel free to add `std::in_place_type_t` support).
// - `non_value_result::from_exception_wrapper()` only when you MUST pay
// for RTTI, such as "thrown exceptions".
//
// See `OperationCancelled.h` for how to handle cancellation. In short: use
// `get_exception<MyErr>(res)` or `has_stopped()`.
//
// These internal-only functions let the `folly::coro` implementation ingest
// `std::exception_ptr`s containing `OperationCancelled` made via
// `folly::coro::co_cancelled`, without incurring the 20-80ns+ cost of
// eagerly eagerly testing whether it contains `OperationCancelled`.
static non_value_result make_legacy_error_or_cancellation(
exception_wrapper ew) {
return {std::in_place, std::move(ew)};
}
exception_wrapper get_legacy_error_or_cancellation() && {
return std::move(ew_);
}
};
template <typename T = void>
class result;
namespace detail {
template <typename>
struct result_promise_return;
template <typename, typename = void>
struct result_promise;
struct result_await_suspender;
// These errors are `detail` because they are only exposed on invariant
// violations in opt builds -- they are NOT part of the public API.
struct bad_result_access_error : public std::exception {};
// Future: Remove this one when we can use never-empty `std::expected`.
struct empty_result_error : public std::exception {};
// Future: To mitigate the risk of `bad_alloc` at runtime, these singletons
// should be eagerly instantiated at program start. One way is to have a
// `shouldEagerInit` singleton in charge of this, and tell the users to do
// this on startup:
// folly::SingletonVault::singleton()->doEagerInit();
const non_value_result& dfatal_get_empty_result_error();
const non_value_result& dfatal_get_bad_result_access_error();
template <typename T>
using result_ref_wrap = std::conditional_t< // Reused by `result_generator`
std::is_rvalue_reference_v<T>,
rvalue_reference_wrapper<std::remove_reference_t<T>>,
std::conditional_t<
std::is_lvalue_reference_v<T>,
std::reference_wrapper<std::remove_reference_t<T>>,
T>>;
// Shared implementation for `T` non-`void` and `void`
template <typename Derived, typename T>
class result_crtp {
static_assert(!std::is_same_v<non_value_result, std::remove_cvref_t<T>>);
static_assert(!std::is_same_v<stopped_result_t, std::remove_cvref_t<T>>);
public:
using value_type = T;
protected:
using storage_type = detail::result_ref_wrap<lift_unit_t<T>>;
static_assert(!std::is_reference_v<storage_type>);
using expected_t = Expected<storage_type, non_value_result>;
expected_t exp_;
template <typename>
friend class folly::result; // The simple conversion ctor uses `exp_`
friend struct detail::result_promise<T>;
friend struct detail::result_promise_return<T>;
friend struct detail::result_await_suspender;
friend inline bool operator==(const result_crtp& a, const result_crtp& b) {
// FIXME: This logic is meant to follow `std::expected`, so once that's in
// use, this operator becomes `a.exp_ == b.exp_`, or simply ` = default;`.
if (a.exp_.hasValue()) {
return b.exp_.hasValue() && a.exp_.value() == b.exp_.value();
} else if (a.exp_.hasError()) {
return b.exp_.hasError() && a.exp_.error() == b.exp_.error();
} else { // `a` empty
return b.is_expected_empty(); // equal iff both are empty
}
}
template <typename ResultT>
static Derived rewrapping_result_convert(ResultT&& rt) {
static_assert(is_instantiation_of_v<result, std::remove_cvref_t<ResultT>>);
if (FOLLY_LIKELY(rt.has_value())) {
// Implicitly convert `ResultT::value_type` to `Derived`.
return {std::forward<ResultT>(rt).value_or_throw()};
}
// `Derived` lets the rewrapping conversion copy a non-value state
return Derived{std::forward<ResultT>(rt).non_value()}; // Rewrap non-value
}
struct private_copy_t {};
result_crtp(private_copy_t, const Derived& that) : exp_(that.exp_) {}
template <typename ExpT>
result_crtp(std::in_place_t, ExpT&& exp) : exp_(static_cast<ExpT&&>(exp)) {}
// As of D42260201, `folly::Expected` coroutines use an empty `Expected`
// as the default storage for a promise return object. Here, we replicate
// that pattern, see `result_promise_return`.
explicit result_crtp(expected_detail::EmptyTag tag) noexcept : exp_{tag} {}
result_crtp(expected_detail::EmptyTag tag, Derived*& pointer) noexcept
: exp_{tag} {
pointer = static_cast<Derived*>(this);
}
// Not for direct use
~result_crtp() = default;
void throw_if_no_value() const {
if (FOLLY_UNLIKELY(exp_.hasError())) {
exp_.error().throw_exception();
} else if (FOLLY_UNLIKELY(!exp_.hasValue())) {
detail::dfatal_get_empty_result_error().throw_exception();
}
}
bool is_expected_empty() const {
// We're checking for an `EmptyTag`-constructed `Expected`, so this
// would be ideal, but that detail isn't public:
// exp_.which_ == expected_detail::Which::eEmpty
return !(exp_.hasValue() || exp_.hasError());
}
public:
/********* Construction & assignment for `T` `void` and non-`void` **********/
/// Movable, so long as `T` is.
result_crtp(result_crtp&&) = default;
result_crtp& operator=(result_crtp&&) = default;
/// `result<T>` has an explicit `.copy()` method instead of a standard copy
/// constructor. This was done because `result` is intended to act as cheap
/// plumbing for function-result-or-error, and
/// - Copying `T` is almost always a performance bug in this setting, but
/// see the below carve-out for "cheap-to-copy `T`".
/// - Copying `std::exception_ptr` also has atomic costs (~25ns).
Derived copy() const {
return Derived{private_copy_t{}, static_cast<const Derived&>(*this)};
}
result_crtp(const result_crtp&) = delete;
result_crtp& operator=(const result_crtp&) = delete;
/// Implicit constructor to allow returning `stopped_result` from `result`
/// coroutines & functions.
///
/// This forbids `result<stopped_result_t>` (`static_assert` above).
/*implicit*/ result_crtp(stopped_result_t s)
: exp_(Unexpected{non_value_result{s}}) {}
/// Implicitly movable / explicitly copyable from `non_value_result` to
/// make it easy to return `resT1.non_value()` in a `result<T2>` function.
///
/// This forbids `result<non_value_result>` (`static_assert` above).
/*implicit*/ result_crtp(non_value_result&& nvr)
: exp_(Unexpected{std::move(nvr)}) {}
explicit result_crtp(const non_value_result& nvr) : exp_(Unexpected{nvr}) {}
/// Fallible copy/move conversion -- unlike the "simple" conversion, this can
/// plausibly apply for `T` void.
///
/// If a user type has a fallible conversion: `U` -> `result<T>`, implicitly
/// convert `result<U>` into `result<T>`, and rewrap any conversion error.
/// The test `fallibleConversion` explains why it has to be **implicit**.
///
/// This helps with `for` loops that iterate over `result<U>`. This loop:
/// auto uGen = generate_result<U>();
/// for (result<T> mv: uGen) {}
/// expands to:
/// result<T> mv = *loopIter;
/// The RHS is usually `result<U>&`, or `result<U>&&` if `U` is an rref.
///
/// As with the simple conversion, prefer move conversions in hot code.
template <class Arg, typename ResultT = std::remove_cvref_t<Arg>>
requires(
!std::is_same_v<ResultT, Derived> && // Not a move/copy ctor.
// Avoid ambiguity with the above "simple conversion"
!std::is_constructible_v<expected_t, typename ResultT::expected_t &&>)
/*implicit*/ result_crtp(Arg&& rt)
: result_crtp(rewrapping_result_convert(std::forward<Arg>(rt))) {}
/***************** Accessors for `T` `void` and non-`void` ******************/
bool has_value() const { return exp_.hasValue(); }
// Also see `has_stopped()` below!
/// Non-value access should be used SPARINGLY!
///
/// Normally, you would:
/// - `folly::get_exception<Ex>(res)` to test for a specific error.
/// - `res.has_stopped()` to test for cancellation.
/// - `co_await std::move(res)` to propagate unhandled error/cancellation
/// in a `result` sync coroutine.
/// - `co_await co_ready(std::move(res))` to propagate unhandled states in
/// a `folly::coro` async coroutine.
///
/// Design notes:
///
/// There is no mutable `&` overload so that we can return singleton
/// invariant-violation exceptions for `dfatal_..._error()` in opt builds:
/// - `folly::Expected` is empty due to an `operator=` exception
/// - Calling `non_value()` when `has_value() == true` -- UB in
/// `std::expected`
/// With folly-internal optimizations (see `extract_exception_ptr`), moving
/// `std::exception_ptr` takes 0.5ns, vs ~25ns for a copy.
///
/// If there is a good use-case for mutating the non-value state inside
/// `result`, we could offer `set_non_value()` with different semantics.
///
/// Future: when I have the appropriate error-path benchmark, try moving the
/// 2 unlikely branches into a .cpp helper, that might help perf.
non_value_result non_value() && {
if (FOLLY_LIKELY(exp_.hasError())) {
return std::move(exp_).error();
} else if (exp_.hasValue()) {
return detail::dfatal_get_bad_result_access_error();
} else {
return detail::dfatal_get_empty_result_error();
}
}
const non_value_result& non_value() const& {
if (FOLLY_LIKELY(exp_.hasError())) {
return exp_.error();
} else if (exp_.hasValue()) {
return detail::dfatal_get_bad_result_access_error();
} else {
return detail::dfatal_get_empty_result_error();
}
}
// Syntax sugar to minimize the chances that end-users need `non_value()`.
bool has_stopped() const { return !has_value() && non_value().has_stopped(); }
/********************************* Protocols ********************************/
// `result` is a short-circuiting coroutine.
using promise_type = detail::result_promise<T>;
// Implement the `folly::get_exception<Ex>(res)` protocol
template <typename Ex>
Ex* get_mutable_exception(get_exception_tag_t) noexcept {
if (!exp_.hasError()) {
return nullptr;
}
return folly::get_mutable_exception<Ex>(exp_.error());
}
template <typename Ex>
const Ex* get_exception(get_exception_tag_t) const noexcept {
if (!exp_.hasError()) {
return nullptr;
}
return folly::get_exception<Ex>(exp_.error());
}
};
} // namespace detail
// The default specialization is non-`void` (but `result<>` defaults to `void`)
template <typename T>
class FOLLY_NODISCARD [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] result final
: public detail::result_crtp<result<T>, T> {
private:
template <typename, typename>
friend class detail::result_crtp; // `ResultT::expected_t` in `requires`
template <typename>
friend class result; // `ResultT::expected_t` in `requires`
using base = typename detail::result_crtp<result<T>, T>;
using typename base::expected_t;
// For `T` non-`void`, we store either `T` or a ref wrapper.
using ref_wrapped_t = typename base::storage_type;
protected:
FOLLY_GTEST_FRIEND_TEST(Result, BadEmptyStateInt);
FOLLY_GTEST_FRIEND_TEST(Result, BadEmptyStateString);
public:
using detail::result_crtp<result<T>, T>::result_crtp;
/// Not default-constructible yet, since the utility is debatable. If we
/// were to later make `result` default-constructible, it should follow
/// `std::expected` semantics, as below. As of now, there are only a couple
/// of tests in `ResultTest.cpp` marked "not default-constructible".
/*
/// Default-construct as `std::expected` would, and unlike `folly::Expected`
result() noexcept(noexcept(expected_t(ref_wrapped_t{})))
requires std::is_default_constructible_v<ref_wrapped_t>
: base{std::in_place, ref_wrapped_t{}} {}
TEST(result, defaultCtor) {
result<> mVoid;
EXPECT_TRUE(mVoid.has_value());
result<int> mInt;
EXPECT_EQ(0, mInt.value_or_throw());
}
*/
/// Copy- & move-conversion from a reference wrapper.
///
/// Implicit to allow returning `std::ref(memberVar_)` from member functions.
/* implicit */ result(ref_wrapped_t t) noexcept
requires std::is_reference_v<T>
: base{std::in_place, std::move(t)} {}
/// Move-construct `result<T>` from the underlying value type `T`.
///
/// Implicit to allow `result<T>` functions to return `T{}` etc.
/* implicit */ result(T&& t) noexcept(noexcept(expected_t(std::move(t))))
requires(
!std::is_reference_v<T> && std::is_constructible_v<expected_t, T &&>)
: base{std::in_place, std::move(t)} {}
result& operator=(T&& t) noexcept(
std::is_nothrow_assignable_v<expected_t, T&&>)
requires(!std::is_reference_v<T> && std::is_assignable_v<expected_t, T &&>)
{
this->exp_ = std::move(t);
return *this;
}
/// Copy underlying `T`, but ONLY when small & trivially copyable.
/// Implicit, so that e.g. `result<int> memberFn()` can return `memVar_`.
//
/// These are a special case because such copies are cheap*, and because
/// good alternatives for populating trivially copiable data are few:
/// - Copy-construct the value into the `result`.
/// - Less efficient: Default-initialize the `result` and assign a
/// `folly::copy()`, or use a mutable value reference to populate it.
/// - Future: Implement in-place construction, to handle very hot code.
///
/// This constructor is deliberately restricted to objects that fit in a
/// cache-line. This is a heuristic to require larger copies to be explicit
/// via `folly::copy()`. If it proves fragile across different
/// architectures, it can be relaxed later.
///
/// Notes:
/// - For now, we omitted the analogous ctor to copy `result<V&>`, for the
/// reason that it's much less common than wanting to write e.g.
/// `result<int> r{intVar}`. It can be added later if strongly needed.
/// - We don't need copy ctors for `co_return varOfTypeT;` because this is
/// an "implicitly movable context" in the C++ spec, so a move ctor is
/// automatically considered as the first option.
/* implicit */ result(const T& t) noexcept(noexcept(expected_t(t)))
requires(
!std::is_reference_v<T> &&
std::is_constructible_v<expected_t, const T&> &&
std::is_trivially_copyable_v<T> &&
sizeof(T) <= hardware_constructive_interference_size)
: base{std::in_place, t} {}
/// No copy assignment. When appropriate, use a mutable `value_or_throw()`
/// reference, or assign `folly::copy(rhs)` to be explicit.
/// Simple copy/move conversion; `result_crtp` also has a fallible conversion.
///
/// Convert `result<U>` to `result<T>` if:
/// - `U` is a value type that is copy/move convertible to `T`.
/// - `U` is a reference whose ref-wrapper is converible to `T`.
/// The test `simpleConversion` shows why this was made implicit.
///
/// In hot code, prefer to convert from an rvalue (move conversion), because
/// that avoids the ~25ns atomic overhead of copying the `std::exception_ptr`.
template <class Arg, typename ResultT = std::remove_cvref_t<Arg>>
requires(
!std::is_same_v<ResultT, result> && // Not a move/copy ctor
// NB: This won't implicitly copy `non_value_result` since the
// underlying `Expected` is only constructible from `Unexpected`.
std::is_constructible_v<expected_t, typename ResultT::expected_t &&>)
/* implicit */ result(Arg&& that)
: base{std::in_place, std::forward<Arg>(that).exp_} {
static_assert(is_instantiation_of_v<result, ResultT>);
}
/// Retrieve non-reference `T`
const T& value_or_throw() const&
requires(!std::is_reference_v<T>)
{
this->throw_if_no_value();
return *this->exp_;
}
T& value_or_throw() &
requires(!std::is_reference_v<T>)
{
this->throw_if_no_value();
return *this->exp_;
}
const T&& value_or_throw() const&&
requires(!std::is_reference_v<T>)
{
this->throw_if_no_value();
return *std::move(this->exp_);
}
T&& value_or_throw() &&
requires(!std::is_reference_v<T>)
{
this->throw_if_no_value();
return *std::move(this->exp_);
}
/// Retrieve reference `T`.
///
/// NB Unlike the value-type versions, these can't mutate the reference
/// wrapper inside `this`. Assign a ref-wrapper to `res` to do that.
///
/// L-value refs follow `std::reference_wrapper`, exposing the underlying ref
/// type regardless of the instance's qualification. We never add `const`
/// for reasons sketched in the test `checkAwaitResumeTypeForRefResult`.
T value_or_throw() const&
requires std::is_lvalue_reference_v<T>
{
this->throw_if_no_value();
return this->exp_->get();
}
// R-value refs follow `folly::rvalue_reference_wrapper`. They model
// single-use references, and thus require `&&` qualification.
T value_or_throw() &&
requires std::is_rvalue_reference_v<T>
{
this->throw_if_no_value();
return std::move(*std::move(this->exp_)).get();
}
};
// Specialization for `T = void` aka `result<>`.
template <>
class FOLLY_NODISCARD [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] result<void>
final : public detail::result_crtp<result<void>, void> {
private:
using base = detail::result_crtp<result<void>, void>;
public:
using base::result_crtp;
// Unlike `result<T>`, default-constructing `result<void>` seems fine.
// Specifically: `std::expected<void>` and `Try<void>` actually agree on the
// semantics (yes, `Try` is internally inconsistent) -- and this is the most
// obvious way to get a value-state `result<>`.
result() : base(std::in_place, unit) {}
void value_or_throw() const { this->throw_if_no_value(); }
};
// Type trait to test if a type is a `result`.
template <typename T>
struct is_result : std::false_type {};
template <typename T>
struct is_result<result<T>> : std::true_type {};
// This short-circuiting coroutine implementation was modeled on
// `folly/Expected.h`, which is likely to follow the state of the art in
// compiler support & optimizations. So, if you're looking at this, please
// compare it to the original, and backport any improvements here.
namespace detail {
template <typename>
struct result_promise_base;
template <typename T>
struct result_promise_return {
result<T> storage_{expected_detail::EmptyTag{}};
result<T>*& pointer_;
/* implicit */ result_promise_return(result_promise_base<T>& p) noexcept
: pointer_{p.value_} {
pointer_ = &storage_;
}
result_promise_return(result_promise_return const&) = delete;
void operator=(result_promise_return const&) = delete;
result_promise_return(result_promise_return&&) = delete;
void operator=(result_promise_return&&) = delete;
// letting dtor be trivial makes the coroutine crash
// TODO: fix clang/llvm codegen
~result_promise_return() {}
/* implicit */ operator result<T>() {
// D42260201: handle both deferred and eager return-object conversion
// behaviors see docs for detect_promise_return_object_eager_conversion
if (coro::detect_promise_return_object_eager_conversion()) {
assert(storage_.is_expected_empty());
return result<T>{expected_detail::EmptyTag{}, pointer_}; // eager
} else {
assert(!storage_.is_expected_empty());
return std::move(storage_); // deferred
}
}
};
template <typename T>
struct result_promise_base {
result<T>* value_ = nullptr;
result_promise_base() = default;
result_promise_base(result_promise_base const&) = delete;
void operator=(result_promise_base const&) = delete;
result_promise_base(result_promise_base&&) = delete;
void operator=(result_promise_base&&) = delete;
~result_promise_base() = default;
FOLLY_NODISCARD std::suspend_never initial_suspend() const noexcept {
return {};
}
FOLLY_NODISCARD std::suspend_never final_suspend() const noexcept {
return {};
}
void unhandled_exception() noexcept {
// We're making a `result`, so it's OK to forward all exceptions into it,
// including `OperationCancelled`.
*value_ = non_value_result::make_legacy_error_or_cancellation(
exception_wrapper{std::current_exception()});
}
result_promise_return<T> get_return_object() noexcept { return *this; }
};
template <typename T>
struct result_promise<T, typename std::enable_if<!std::is_void_v<T>>::type>
: public result_promise_base<T> {
// For reference types, this deliberately requires users to `co_return`
// one of `std::ref`, `std::cref`, or `folly::rref`.
//
// The default for `U` is tested in `returnImplicitCtor`.
template <typename U = T>
void return_value(U&& u) {
auto& v = *this->value_;
expected_detail::ExpectedHelper::assume_empty(v.exp_);
v = static_cast<U&&>(u);
}
};
template <typename T>
struct result_promise<T, typename std::enable_if<std::is_void_v<T>>::type>
: public result_promise_base<T> {
// When the coroutine uses `return;` you can fail via `co_await err`.
void return_void() { this->value_->exp_.emplace(unit); }
};
template <typename T>
using result_promise_handle = std::coroutine_handle<result_promise<T>>;
// This is separate to let `result_generator` reuse the awaitables below.
struct result_await_suspender {
// Future: check if all these `FOLLY_ALWAYS_INLINE`s aren't a pessimization.
template <typename T, typename U>
FOLLY_ALWAYS_INLINE void operator()(T&& t, result_promise_handle<U> handle) {
auto& v = *handle.promise().value_;
expected_detail::ExpectedHelper::assume_empty(v.exp_);
// `T` can be `non_value_result&&`, or one of a few `result<T>` refs.
if constexpr (std::is_same_v<non_value_result, std::remove_cvref_t<T>>) {
v.exp_ = Unexpected{std::forward<T>(t)};
} else {
v.exp_ = Unexpected{std::forward<T>(t).non_value()};
}
// Abort the rest of the coroutine. resume() is not going to be called
handle.destroy();
}
};
// There's no `result` in the name as a hint to lift this to a shared header as
// soon as another usecase arises.
template <typename AwaitSuspender>
struct non_value_awaitable {
non_value_result non_value_;
constexpr std::false_type await_ready() const noexcept { return {}; }
[[noreturn]] void await_resume() {
compiler_may_unsafely_assume_unreachable();
}
FOLLY_ALWAYS_INLINE void await_suspend(auto h) {
AwaitSuspender()(std::move(non_value_), h);
}
};
template <typename T, typename AwaitSuspender>
struct result_owning_awaitable {
result<T> storage_;
bool await_ready() const noexcept { return storage_.has_value(); }
drop_unit_t<T> await_resume() { return std::move(storage_).value_or_throw(); }
FOLLY_ALWAYS_INLINE void await_suspend(auto h) {
AwaitSuspender()(std::move(storage_), h);
}
};
// We won't have a `folly::rvalue_reference_wrapper` counterpart because
// awaiting rvalue `result`s is handled by `result_owning_awaitable`, which
// avoids exposing some dangling reference footguns to the user.
template <
typename T,
template <typename>
class ConstWrapper,
typename AwaitSuspender>
struct result_ref_awaitable {
using ResultT = ConstWrapper<result<T>>;
constexpr static bool kIsConstRef = !std::is_same_v<ResultT, result<T>>;
std::reference_wrapper<ResultT> storage_;
bool await_ready() const noexcept { return storage_.get().has_value(); }
// Awaiting a ref to `result<Value>` returns a ref to the value.
T& await_resume()
requires(!std::is_reference_v<T> && !kIsConstRef)
{
return storage_.get().value_or_throw();
}
const T& await_resume()
requires(!std::is_reference_v<T> && kIsConstRef)
{
return storage_.get().value_or_throw();
}
// Awaiting a ref to `result<Reference>` returns the reference itself.
T await_resume()
requires std::is_reference_v<T>
{
return storage_.get().value_or_throw();
}
FOLLY_ALWAYS_INLINE void await_suspend(auto h) {
// We can't move the error even out of a mutable l-value reference to
// `result`, because the user isn't counting on `co_await std::ref(m)` to
// mutate the `result`.
AwaitSuspender()(storage_.get(), h);
}
};
} // namespace detail
// co_await stopped_result
inline auto /* implicit */ operator co_await(stopped_result_t s) {
return detail::non_value_awaitable<detail::result_await_suspender>{
.non_value_ = non_value_result{s}};
}
// co_await std::move(res).non_value()
//
// Pass-by-&& to discourage accidental copies of `std::exception_ptr`.
inline auto /* implicit */ operator co_await(non_value_result && nvr) {
return detail::non_value_awaitable<detail::result_await_suspender>{
.non_value_ = std::move(nvr)};
}
// co_await resultFunc()
//
// DO NOT add a copyable overload for small, trivially copyable types,
// since this is (a) rare, (b) will make the error path slower. See the
// discussion of `co_await std::{move,ref,cref}` in `result.md`.
template <typename T>
auto /* implicit */ operator co_await(result<T>&& r) {
return detail::result_owning_awaitable<T, detail::result_await_suspender>{
.storage_ = std::move(r)};
}
// co_await std::ref(resultVal)
template <typename T>
auto /* implicit */ operator co_await(std::reference_wrapper<result<T>> rr) {
return detail::result_ref_awaitable<
T,
std::type_identity_t,
detail::result_await_suspender>{.storage_ = std::move(rr)};
}
// co_await std::cref(resultVal)
template <typename T>
auto /* implicit */ operator co_await(
std::reference_wrapper<const result<T>> cr) {
return detail::
result_ref_awaitable<T, std::add_const_t, detail::result_await_suspender>{
.storage_ = std::move(cr)};
}
/// Wraps the return value from the lambda `fn` in a `result`, putting any
/// thrown exception into its "error" state.
///
/// return result_catch_all([&](){ return riskyWork(); });
///
/// Useful when you need a subroutine **definitely** not to throw. In contrast:
/// - `result<>` coroutines catch unhandled exceptions, but can throw due to
/// argument copy/move ctors, or due to `bad_alloc`.
/// - Like all functions, `result<>` non-coroutines let exceptions fly.
template <typename F>
// Wrap the return type of `fn` with `result` unless it already is `result`.
typename std::conditional_t<
is_instantiation_of_v<result, std::invoke_result_t<F>>,
std::invoke_result_t<F>,
result<std::invoke_result_t<F>>>
result_catch_all(F&& fn) noexcept {
try {
if constexpr (std::is_void_v<std::invoke_result_t<F>>) {
static_cast<F&&>(fn)();
return {};
} else {
return static_cast<F&&>(fn)();
}
} catch (...) {
// We're a making `result`, so it's OK to forward all exceptions into it,
// including `OperationCancelled`.
return non_value_result::make_legacy_error_or_cancellation(
exception_wrapper{std::current_exception()});
}
}
} // namespace folly
#endif // FOLLY_HAS_RESULT