// // Copyright 2017 Asylo authors // // 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. // // Adapted from Asylo #pragma once #include #include #include #include #include #include "arrow/status.h" #include "arrow/util/aligned_storage.h" #include "arrow/util/compare.h" namespace arrow { template struct EnsureResult; namespace internal { ARROW_EXPORT void DieWithMessage(const std::string& msg); ARROW_EXPORT void InvalidValueOrDie(const Status& st); } // namespace internal /// A class for representing either a usable value, or an error. /// /// A Result object either contains a value of type `T` or a Status object /// explaining why such a value is not present. The type `T` must be /// copy-constructible and/or move-constructible. /// /// The state of a Result object may be determined by calling ok() or /// status(). The ok() method returns true if the object contains a valid value. /// The status() method returns the internal Status object. A Result object /// that contains a valid value will return an OK Status for a call to status(). /// /// A value of type `T` may be extracted from a Result object through a call /// to ValueOrDie(). This function should only be called if a call to ok() /// returns true. Sample usage: /// /// ``` /// arrow::Result result = CalculateFoo(); /// if (result.ok()) { /// Foo foo = result.ValueOrDie(); /// foo.DoSomethingCool(); /// } else { /// ARROW_LOG(ERROR) << result.status(); /// } /// ``` /// /// If `T` is a move-only type, like `std::unique_ptr<>`, then the value should /// only be extracted after invoking `std::move()` on the Result object. /// Sample usage: /// /// ``` /// arrow::Result> result = CalculateFoo(); /// if (result.ok()) { /// std::unique_ptr foo = std::move(result).ValueOrDie(); /// foo->DoSomethingCool(); /// } else { /// ARROW_LOG(ERROR) << result.status(); /// } /// ``` /// /// Result is provided for the convenience of implementing functions that /// return some value but may fail during execution. For instance, consider a /// function with the following signature: /// /// ``` /// arrow::Status CalculateFoo(int *output); /// ``` /// /// This function may instead be written as: /// /// ``` /// arrow::Result CalculateFoo(); /// ``` template class [[nodiscard]] Result : public util::EqualityComparable> { template friend class Result; static_assert(!std::is_same::value, "this assert indicates you have probably made a metaprogramming error"); public: using ValueType = T; /// Constructs a Result object that contains a non-OK status. /// /// This constructor is marked `explicit` to prevent attempts to `return {}` /// from a function with a return type of, for example, /// `Result>`. While `return {}` seems like it would return /// an empty vector, it will actually invoke the default constructor of /// Result. explicit Result() noexcept // NOLINT(runtime/explicit) : status_(Status::UnknownError("Uninitialized Result")) {} ~Result() noexcept { Destroy(); } /// Constructs a Result object with the given non-OK Status object. All /// calls to ValueOrDie() on this object will abort. The given `status` must /// not be an OK status, otherwise this constructor will abort. /// /// This constructor is not declared explicit so that a function with a return /// type of `Result` can return a Status object, and the status will be /// implicitly converted to the appropriate return type as a matter of /// convenience. /// /// \param status The non-OK Status object to initialize to. Result(const Status& status) noexcept // NOLINT(runtime/explicit) : status_(status) { if (ARROW_PREDICT_FALSE(status.ok())) { internal::DieWithMessage(std::string("Constructed with a non-error status: ") + status.ToString()); } } /// Constructs a Result object that contains `value`. The resulting object /// is considered to have an OK status. The wrapped element can be accessed /// with ValueOrDie(). /// /// This constructor is made implicit so that a function with a return type of /// `Result` can return an object of type `U &&`, implicitly converting /// it to a `Result` object. /// /// Note that `T` must be implicitly constructible from `U`, and `U` must not /// be a (cv-qualified) Status or Status-reference type. Due to C++ /// reference-collapsing rules and perfect-forwarding semantics, this /// constructor matches invocations that pass `value` either as a const /// reference or as an rvalue reference. Since Result needs to work for both /// reference and rvalue-reference types, the constructor uses perfect /// forwarding to avoid invalidating arguments that were passed by reference. /// See http://thbecker.net/articles/rvalue_references/section_08.html for /// additional details. /// /// \param value The value to initialize to. template ::value && std::is_convertible::value && !std::is_same::type>::type, Status>::value>::type> Result(U&& value) noexcept { // NOLINT(runtime/explicit) ConstructValue(std::forward(value)); } /// Constructs a Result object that contains `value`. The resulting object /// is considered to have an OK status. The wrapped element can be accessed /// with ValueOrDie(). /// /// This constructor is made implicit so that a function with a return type of /// `Result` can return an object of type `T`, implicitly converting /// it to a `Result` object. /// /// \param value The value to initialize to. // NOTE `Result(U&& value)` above should be sufficient, but some compilers // fail matching it. Result(T&& value) noexcept { // NOLINT(runtime/explicit) ConstructValue(std::move(value)); } /// Copy constructor. /// /// This constructor needs to be explicitly defined because the presence of /// the move-assignment operator deletes the default copy constructor. In such /// a scenario, since the deleted copy constructor has stricter binding rules /// than the templated copy constructor, the templated constructor cannot act /// as a copy constructor, and any attempt to copy-construct a `Result` /// object results in a compilation error. /// /// \param other The value to copy from. Result(const Result& other) noexcept : status_(other.status_) { if (ARROW_PREDICT_TRUE(status_.ok())) { ConstructValue(other.ValueUnsafe()); } } /// Templatized constructor that constructs a `Result` from a const /// reference to a `Result`. /// /// `T` must be implicitly constructible from `const U &`. /// /// \param other The value to copy from. template ::value && std::is_convertible::value>::type> Result(const Result& other) noexcept : status_(other.status_) { if (ARROW_PREDICT_TRUE(status_.ok())) { ConstructValue(other.ValueUnsafe()); } } /// Copy-assignment operator. /// /// \param other The Result object to copy. Result& operator=(const Result& other) noexcept { // Check for self-assignment. if (ARROW_PREDICT_FALSE(this == &other)) { return *this; } Destroy(); status_ = other.status_; if (ARROW_PREDICT_TRUE(status_.ok())) { ConstructValue(other.ValueUnsafe()); } return *this; } /// Templatized constructor which constructs a `Result` by moving the /// contents of a `Result`. `T` must be implicitly constructible from `U /// &&`. /// /// Sets `other` to contain a non-OK status with a`StatusError::Invalid` /// error code. /// /// \param other The Result object to move from and set to a non-OK status. template ::value && std::is_convertible::value>::type> Result(Result&& other) noexcept { if (ARROW_PREDICT_TRUE(other.status_.ok())) { status_ = std::move(other.status_); ConstructValue(other.MoveValueUnsafe()); } else { // If we moved the status, the other status may become ok but the other // value hasn't been constructed => crash on other destructor. status_ = other.status_; } } /// Move-assignment operator. /// /// Sets `other` to an invalid state.. /// /// \param other The Result object to assign from and set to a non-OK /// status. Result& operator=(Result&& other) noexcept { // Check for self-assignment. if (ARROW_PREDICT_FALSE(this == &other)) { return *this; } Destroy(); if (ARROW_PREDICT_TRUE(other.status_.ok())) { status_ = std::move(other.status_); ConstructValue(other.MoveValueUnsafe()); } else { // If we moved the status, the other status may become ok but the other // value hasn't been constructed => crash on other destructor. status_ = other.status_; } return *this; } /// Compare to another Result. bool Equals(const Result& other) const { if (ARROW_PREDICT_TRUE(status_.ok())) { return other.status_.ok() && ValueUnsafe() == other.ValueUnsafe(); } return status_ == other.status_; } /// Indicates whether the object contains a `T` value. Generally instead /// of accessing this directly you will want to use ASSIGN_OR_RAISE defined /// below. /// /// \return True if this Result object's status is OK (i.e. a call to ok() /// returns true). If this function returns true, then it is safe to access /// the wrapped element through a call to ValueOrDie(). constexpr bool ok() const { return status_.ok(); } /// \brief Equivalent to ok(). // operator bool() const { return ok(); } /// Gets the stored status object, or an OK status if a `T` value is stored. /// /// \return The stored non-OK status object, or an OK status if this object /// has a value. constexpr const Status& status() const { return status_; } /// Gets the stored `T` value. /// /// This method should only be called if this Result object's status is OK /// (i.e. a call to ok() returns true), otherwise this call will abort. /// /// \return The stored `T` value. const T& ValueOrDie() const& { if (ARROW_PREDICT_FALSE(!ok())) { internal::InvalidValueOrDie(status_); } return ValueUnsafe(); } const T& operator*() const& { return ValueOrDie(); } const T* operator->() const { return &ValueOrDie(); } /// Gets a mutable reference to the stored `T` value. /// /// This method should only be called if this Result object's status is OK /// (i.e. a call to ok() returns true), otherwise this call will abort. /// /// \return The stored `T` value. T& ValueOrDie() & { if (ARROW_PREDICT_FALSE(!ok())) { internal::InvalidValueOrDie(status_); } return ValueUnsafe(); } T& operator*() & { return ValueOrDie(); } T* operator->() { return &ValueOrDie(); } /// Moves and returns the internally-stored `T` value. /// /// This method should only be called if this Result object's status is OK /// (i.e. a call to ok() returns true), otherwise this call will abort. The /// Result object is invalidated after this call and will be updated to /// contain a non-OK status. /// /// \return The stored `T` value. T ValueOrDie() && { if (ARROW_PREDICT_FALSE(!ok())) { internal::InvalidValueOrDie(status_); } return MoveValueUnsafe(); } T operator*() && { return std::move(*this).ValueOrDie(); } /// Helper method for implementing Status returning functions in terms of semantically /// equivalent Result returning functions. For example: /// /// Status GetInt(int *out) { return GetInt().Value(out); } template ::value>::type> Status Value(U* out) && { if (!ok()) { return status(); } *out = U(MoveValueUnsafe()); return Status::OK(); } /// Move and return the internally stored value or alternative if an error is stored. T ValueOr(T alternative) && { if (!ok()) { return alternative; } return MoveValueUnsafe(); } /// Retrieve the value if ok(), falling back to an alternative generated by the provided /// factory template T ValueOrElse(G&& generate_alternative) && { if (ok()) { return MoveValueUnsafe(); } return std::forward(generate_alternative)(); } /// Apply a function to the internally stored value to produce a new result or propagate /// the stored error. template typename EnsureResult()(std::declval()))>::type Map( M&& m) && { if (!ok()) { return status(); } return std::forward(m)(MoveValueUnsafe()); } /// Apply a function to the internally stored value to produce a new result or propagate /// the stored error. template typename EnsureResult()(std::declval()))>::type Map(M&& m) const& { if (!ok()) { return status(); } return std::forward(m)(ValueUnsafe()); } /// Cast the internally stored value to produce a new result or propagate the stored /// error. template ::value>::type> Result As() && { if (!ok()) { return status(); } return U(MoveValueUnsafe()); } /// Cast the internally stored value to produce a new result or propagate the stored /// error. template ::value>::type> Result As() const& { if (!ok()) { return status(); } return U(ValueUnsafe()); } constexpr const T& ValueUnsafe() const& { return *storage_.get(); } constexpr T& ValueUnsafe() & { return *storage_.get(); } T ValueUnsafe() && { return MoveValueUnsafe(); } T MoveValueUnsafe() { return std::move(*storage_.get()); } private: Status status_; // pointer-sized internal::AlignedStorage storage_; template void ConstructValue(U&& u) noexcept { storage_.construct(std::forward(u)); } void Destroy() noexcept { if (ARROW_PREDICT_TRUE(status_.ok())) { static_assert(offsetof(Result, status_) == 0, "Status is guaranteed to be at the start of Result<>"); storage_.destroy(); } } }; #define ARROW_ASSIGN_OR_RAISE_IMPL(result_name, lhs, rexpr) \ auto&& result_name = (rexpr); \ ARROW_RETURN_IF_(!(result_name).ok(), (result_name).status(), ARROW_STRINGIFY(rexpr)); \ lhs = std::move(result_name).ValueUnsafe(); #define ARROW_ASSIGN_OR_RAISE_NAME(x, y) ARROW_CONCAT(x, y) /// \brief Execute an expression that returns a Result, extracting its value /// into the variable defined by `lhs` (or returning a Status on error). /// /// Example: Assigning to a new value: /// ARROW_ASSIGN_OR_RAISE(auto value, MaybeGetValue(arg)); /// /// Example: Assigning to an existing value: /// ValueType value; /// ARROW_ASSIGN_OR_RAISE(value, MaybeGetValue(arg)); /// /// WARNING: ARROW_ASSIGN_OR_RAISE expands into multiple statements; /// it cannot be used in a single statement (e.g. as the body of an if /// statement without {})! /// /// WARNING: ARROW_ASSIGN_OR_RAISE `std::move`s its right operand. If you have /// an lvalue Result which you *don't* want to move out of cast appropriately. /// /// WARNING: ARROW_ASSIGN_OR_RAISE is not a single expression; it will not /// maintain lifetimes of all temporaries in `rexpr` (e.g. /// `ARROW_ASSIGN_OR_RAISE(auto x, MakeTemp().GetResultRef());` /// will most likely segfault)! #define ARROW_ASSIGN_OR_RAISE(lhs, rexpr) \ ARROW_ASSIGN_OR_RAISE_IMPL(ARROW_ASSIGN_OR_RAISE_NAME(_error_or_value, __COUNTER__), \ lhs, rexpr); namespace internal { template inline const Status& GenericToStatus(const Result& res) { return res.status(); } template inline Status GenericToStatus(Result&& res) { return std::move(res).status(); } } // namespace internal template ::type> R ToResult(T t) { return R(std::move(t)); } template struct EnsureResult { using type = Result; }; template struct EnsureResult> { using type = Result; }; } // namespace arrow