/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* A type suitable for returning either a value or an error from a function. */ #ifndef mozilla_Result_h #define mozilla_Result_h #include #include "mozilla/Alignment.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Types.h" #include "mozilla/TypeTraits.h" #include "mozilla/Variant.h" namespace mozilla { /** * Empty struct, indicating success for operations that have no return value. * For example, if you declare another empty struct `struct OutOfMemory {};`, * then `Result` represents either success or OOM. */ struct Ok {}; template class GenericErrorResult; template class Result; namespace detail { enum class PackingStrategy { Variant, NullIsOk, LowBitTagIsError, PackedVariant, }; template class ResultImplementation; template class ResultImplementation { mozilla::Variant mStorage; public: ResultImplementation(ResultImplementation&&) = default; ResultImplementation(const ResultImplementation&) = default; ResultImplementation& operator=(const ResultImplementation&) = default; ResultImplementation& operator=(ResultImplementation&&) = default; explicit ResultImplementation(V&& aValue) : mStorage(std::forward(aValue)) {} explicit ResultImplementation(const V& aValue) : mStorage(aValue) {} explicit ResultImplementation(E aErrorValue) : mStorage(std::forward(aErrorValue)) {} bool isOk() const { return mStorage.template is(); } // The callers of these functions will assert isOk() has the proper value, so // these functions (in all ResultImplementation specializations) don't need // to do so. V unwrap() { return std::move(mStorage.template as()); } const V& inspect() const { return mStorage.template as(); } E unwrapErr() { return std::move(mStorage.template as()); } const E& inspectErr() const { return mStorage.template as(); } }; /** * mozilla::Variant doesn't like storing a reference. This is a specialization * to store E as pointer if it's a reference. */ template class ResultImplementation { mozilla::Variant mStorage; public: explicit ResultImplementation(V&& aValue) : mStorage(std::forward(aValue)) {} explicit ResultImplementation(const V& aValue) : mStorage(aValue) {} explicit ResultImplementation(E& aErrorValue) : mStorage(&aErrorValue) {} bool isOk() const { return mStorage.template is(); } const V& inspect() const { return mStorage.template as(); } V unwrap() { return std::move(mStorage.template as()); } E& unwrapErr() { return *mStorage.template as(); } const E& inspectErr() const { return *mStorage.template as(); } }; /** * Specialization for when the success type is Ok (or another empty class) and * the error type is a reference. */ template class ResultImplementation { E* mErrorValue; public: explicit ResultImplementation(V) : mErrorValue(nullptr) {} explicit ResultImplementation(E& aErrorValue) : mErrorValue(&aErrorValue) {} bool isOk() const { return mErrorValue == nullptr; } const V& inspect() const = delete; V unwrap() { return V(); } const E& inspectErr() const { return *mErrorValue; } E& unwrapErr() { return *mErrorValue; } }; /** * Specialization for when the success type is Ok (or another empty class) and * the error type is a value type which can never have the value 0 (as * determined by UnusedZero<>). */ template class ResultImplementation { static constexpr E NullValue = E(0); E mErrorValue; public: explicit ResultImplementation(V) : mErrorValue(NullValue) {} explicit ResultImplementation(E aErrorValue) : mErrorValue(aErrorValue) { MOZ_ASSERT(aErrorValue != NullValue); } bool isOk() const { return mErrorValue == NullValue; } const V& inspect() const = delete; V unwrap() { return V(); } const E& inspectErr() const { return mErrorValue; } E unwrapErr() { return std::move(mErrorValue); } }; /** * Specialization for when alignment permits using the least significant bit as * a tag bit. */ template class ResultImplementation { uintptr_t mBits; public: explicit ResultImplementation(V* aValue) : mBits(reinterpret_cast(aValue)) { MOZ_ASSERT((uintptr_t(aValue) % MOZ_ALIGNOF(V)) == 0, "Result value pointers must not be misaligned"); } explicit ResultImplementation(E& aErrorValue) : mBits(reinterpret_cast(&aErrorValue) | 1) { MOZ_ASSERT((uintptr_t(&aErrorValue) % MOZ_ALIGNOF(E)) == 0, "Result errors must not be misaligned"); } bool isOk() const { return (mBits & 1) == 0; } V* inspect() const { return reinterpret_cast(mBits); } V* unwrap() { return inspect(); } E& inspectErr() const { return *reinterpret_cast(mBits ^ 1); } E& unwrapErr() { return inspectErr(); } }; // Return true if any of the struct can fit in a word. template struct IsPackableVariant { struct VEbool { V v; E e; bool ok; }; struct EVbool { E e; V v; bool ok; }; using Impl = typename Conditional::Type; static const bool value = sizeof(Impl) <= sizeof(uintptr_t); }; /** * Specialization for when both type are not using all the bytes, in order to * use one byte as a tag. */ template class ResultImplementation { using Impl = typename IsPackableVariant::Impl; Impl data; public: explicit ResultImplementation(V aValue) { data.v = std::move(aValue); data.ok = true; } explicit ResultImplementation(E aErrorValue) { data.e = std::move(aErrorValue); data.ok = false; } bool isOk() const { return data.ok; } const V& inspect() const { return data.v; } V unwrap() { return std::move(data.v); } const E& inspectErr() const { return data.e; } E unwrapErr() { return std::move(data.e); } }; // To use nullptr as a special value, we need the counter part to exclude zero // from its range of valid representations. // // By default assume that zero can be represented. template struct UnusedZero { static const bool value = false; }; // References can't be null. template struct UnusedZero { static const bool value = true; }; // A bit of help figuring out which of the above specializations to use. // // We begin by safely assuming types don't have a spare bit. template struct HasFreeLSB { static const bool value = false; }; // As an incomplete type, void* does not have a spare bit. template <> struct HasFreeLSB { static const bool value = false; }; // The lowest bit of a properly-aligned pointer is always zero if the pointee // type is greater than byte-aligned. That bit is free to use if it's masked // out of such pointers before they're dereferenced. template struct HasFreeLSB { static const bool value = (alignof(T) & 1) == 0; }; // We store references as pointers, so they have a free bit if a pointer would // have one. template struct HasFreeLSB { static const bool value = HasFreeLSB::value; }; // Select one of the previous result implementation based on the properties of // the V and E types. template struct SelectResultImpl { static const PackingStrategy value = (IsEmpty::value && UnusedZero::value) ? PackingStrategy::NullIsOk : (detail::HasFreeLSB::value && detail::HasFreeLSB::value) ? PackingStrategy::LowBitTagIsError : (IsDefaultConstructible::value && IsDefaultConstructible::value && IsPackableVariant::value) ? PackingStrategy::PackedVariant : PackingStrategy::Variant; using Type = detail::ResultImplementation; }; template struct IsResult : FalseType {}; template struct IsResult> : TrueType {}; } // namespace detail template auto ToResult(Result&& aValue) -> decltype(std::forward>(aValue)) { return std::forward>(aValue); } /** * Result represents the outcome of an operation that can either succeed * or fail. It contains either a success value of type V or an error value of * type E. * * All Result methods are const, so results are basically immutable. * This is just like Variant but with a slightly different API, and the * following cases are optimized so Result can be stored more efficiently: * * - If the success type is Ok (or another empty class) and the error type is a * reference, Result is guaranteed to be pointer-sized and all zero * bits on success. Do not change this representation! There is JIT code that * depends on it. * * - If the success type is a pointer type and the error type is a reference * type, and the least significant bit is unused for both types when stored * as a pointer (due to alignment rules), Result is guaranteed to be * pointer-sized. In this case, we use the lowest bit as tag bit: 0 to * indicate the Result's bits are a V, 1 to indicate the Result's bits (with * the 1 masked out) encode an E*. * * The purpose of Result is to reduce the screwups caused by using `false` or * `nullptr` to indicate errors. * What screwups? See for * a partial list. */ template class MOZ_MUST_USE_TYPE Result final { using Impl = typename detail::SelectResultImpl::Type; Impl mImpl; public: /** Create a success result. */ MOZ_IMPLICIT Result(V&& aValue) : mImpl(std::forward(aValue)) { MOZ_ASSERT(isOk()); } /** Create a success result. */ MOZ_IMPLICIT Result(const V& aValue) : mImpl(aValue) { MOZ_ASSERT(isOk()); } /** Create an error result. */ explicit Result(E aErrorValue) : mImpl(std::forward(aErrorValue)) { MOZ_ASSERT(isErr()); } /** * Implementation detail of MOZ_TRY(). * Create an error result from another error result. */ template MOZ_IMPLICIT Result(GenericErrorResult&& aErrorResult) : mImpl(std::forward(aErrorResult.mErrorValue)) { static_assert(mozilla::IsConvertible::value, "E2 must be convertible to E"); MOZ_ASSERT(isErr()); } /** * Implementation detail of MOZ_TRY(). * Create an error result from another error result. */ template MOZ_IMPLICIT Result(const GenericErrorResult& aErrorResult) : mImpl(aErrorResult.mErrorValue) { static_assert(mozilla::IsConvertible::value, "E2 must be convertible to E"); MOZ_ASSERT(isErr()); } Result(const Result&) = default; Result(Result&&) = default; Result& operator=(const Result&) = default; Result& operator=(Result&&) = default; /** True if this Result is a success result. */ bool isOk() const { return mImpl.isOk(); } /** True if this Result is an error result. */ bool isErr() const { return !mImpl.isOk(); } /** Take the success value from this Result, which must be a success result. */ V unwrap() { MOZ_ASSERT(isOk()); return mImpl.unwrap(); } /** * Take the success value from this Result, which must be a success result. * If it is an error result, then return the aValue. */ V unwrapOr(V aValue) { return MOZ_LIKELY(isOk()) ? mImpl.unwrap() : std::move(aValue); } /** Take the error value from this Result, which must be an error result. */ E unwrapErr() { MOZ_ASSERT(isErr()); return mImpl.unwrapErr(); } /** See the success value from this Result, which must be a success result. */ const V& inspect() const { return mImpl.inspect(); } /** See the error value from this Result, which must be an error result. */ const E& inspectErr() const { MOZ_ASSERT(isErr()); return mImpl.inspectErr(); } /** * Map a function V -> W over this result's success variant. If this result is * an error, do not invoke the function and return a copy of the error. * * Mapping over success values invokes the function to produce a new success * value: * * // Map Result to another Result * Result res(5); * Result res2 = res.map([](int x) { return x * x; }); * MOZ_ASSERT(res2.unwrap() == 25); * * // Map Result to Result * Result res("hello, map!"); * Result res2 = res.map(strlen); * MOZ_ASSERT(res2.unwrap() == 11); * * Mapping over an error does not invoke the function and copies the error: * * Result res(5); * MOZ_ASSERT(res.isErr()); * Result res2 = res.map([](V v) { ... }); * MOZ_ASSERT(res2.isErr()); * MOZ_ASSERT(res2.unwrapErr() == 5); */ template auto map(F f) -> Result { using RetResult = Result; return MOZ_LIKELY(isOk()) ? RetResult(f(unwrap())) : RetResult(unwrapErr()); } /** * Map a function V -> W over this result's error variant. If this result is * a success, do not invoke the function and move the success over. * * Mapping over error values invokes the function to produce a new error * value: * * // Map Result to another Result * Result res(5); * Result res2 = res.mapErr([](int x) { return x * x; }); * MOZ_ASSERT(res2.unwrapErr() == 25); * * // Map Result to Result * Result res("hello, map!"); * Result res2 = res.mapErr(strlen); * MOZ_ASSERT(res2.unwrapErr() == 11); * * Mapping over a success does not invoke the function and copies the error: * * Result res(5); * MOZ_ASSERT(res.isOk()); * Result res2 = res.mapErr([](V v) { ... }); * MOZ_ASSERT(res2.isOk()); * MOZ_ASSERT(res2.unwrap() == 5); */ template auto mapErr(F f) -> Result> { using RetResult = Result>; return isOk() ? RetResult(unwrap()) : RetResult(f(unwrapErr())); } /** * Given a function V -> Result, apply it to this result's success value * and return its result. If this result is an error value, then return a * copy. * * This is sometimes called "flatMap" or ">>=" in other contexts. * * `andThen`ing over success values invokes the function to produce a new * result: * * Result res("hello, andThen!"); * Result res2 = res.andThen([](const char* s) { * return containsHtmlTag(s) * ? Result(Error("Invalid: contains HTML")) * : Result(HtmlFreeString(s)); * } * }); * MOZ_ASSERT(res2.isOk()); * MOZ_ASSERT(res2.unwrap() == HtmlFreeString("hello, andThen!"); * * `andThen`ing over error results does not invoke the function, and just * produces a new copy of the error result: * * Result res("some error"); * auto res2 = res.andThen([](int x) { ... }); * MOZ_ASSERT(res2.isErr()); * MOZ_ASSERT(res.unwrapErr() == res2.unwrapErr()); */ template ::value>::Type> auto andThen(F f) -> decltype(f(*((V*)nullptr))) { return MOZ_LIKELY(isOk()) ? f(unwrap()) : GenericErrorResult(unwrapErr()); } }; /** * A type that auto-converts to an error Result. This is like a Result without * a success type. It's the best return type for functions that always return * an error--functions designed to build and populate error objects. It's also * useful in error-handling macros; see MOZ_TRY for an example. */ template class MOZ_MUST_USE_TYPE GenericErrorResult { E mErrorValue; template friend class Result; public: explicit GenericErrorResult(E aErrorValue) : mErrorValue(std::forward(aErrorValue)) {} }; template inline GenericErrorResult Err(E&& aErrorValue) { return GenericErrorResult(std::forward(aErrorValue)); } } // namespace mozilla /** * MOZ_TRY(expr) is the C++ equivalent of Rust's `try!(expr);`. First, it * evaluates expr, which must produce a Result value. On success, it * discards the result altogether. On error, it immediately returns an error * Result from the enclosing function. */ #define MOZ_TRY(expr) \ do { \ auto mozTryTempResult_ = ::mozilla::ToResult(expr); \ if (MOZ_UNLIKELY(mozTryTempResult_.isErr())) { \ return ::mozilla::Err(mozTryTempResult_.unwrapErr()); \ } \ } while (0) /** * MOZ_TRY_VAR(target, expr) is the C++ equivalent of Rust's `target = * try!(expr);`. First, it evaluates expr, which must produce a Result value. On * success, the result's success value is assigned to target. On error, * immediately returns the error result. |target| must evaluate to a reference * without any side effects. */ #define MOZ_TRY_VAR(target, expr) \ do { \ auto mozTryVarTempResult_ = (expr); \ if (MOZ_UNLIKELY(mozTryVarTempResult_.isErr())) { \ return ::mozilla::Err(mozTryVarTempResult_.unwrapErr()); \ } \ (target) = mozTryVarTempResult_.unwrap(); \ } while (0) #endif // mozilla_Result_h