// Copyright 2018 The Dawn 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. #ifndef COMMON_RESULT_H_ #define COMMON_RESULT_H_ #include "common/Assert.h" #include "common/Compiler.h" #include #include #include // Result is the following sum type (Haskell notation): // // data Result T E = Success T | Error E | Empty // // It is meant to be used as the return type of functions that might fail. The reason for the Empty // case is that a Result should never be discarded, only destructured (its error or success moved // out) or moved into a different Result. The Empty case tags Results that have been moved out and // Result's destructor should ASSERT on it being Empty. // // Since C++ doesn't have efficient sum types for the special cases we care about, we provide // template specializations for them. template class Result; // The interface of Result shoud look like the following. // public: // Result(T&& success); // Result(E&& error); // // Result(Result&& other); // Result& operator=(Result&& other); // // ~Result(); // // bool IsError() const; // bool IsSuccess() const; // // T&& AcquireSuccess(); // E&& AcquireError(); // Specialization of Result for returning errors only via pointers. It is basically a pointer // where nullptr is both Success and Empty. template class DAWN_NO_DISCARD Result { public: Result(); Result(E* error); Result(Result&& other); Result& operator=(Result&& other); ~Result(); bool IsError() const; bool IsSuccess() const; void AcquireSuccess(); E* AcquireError(); private: E* mError = nullptr; }; // Uses SFINAE to try to get alignof(T) but fallback to Default if T isn't defined. template constexpr size_t alignof_if_defined_else_default = Default; template constexpr size_t alignof_if_defined_else_default = alignof(T); // Specialization of Result when both the error an success are pointers. It is implemented as a // tagged pointer. The tag for Success is 0 so that returning the value is fastest. template class DAWN_NO_DISCARD Result { public: static_assert(alignof_if_defined_else_default >= 4, "Result reserves two bits for tagging pointers"); static_assert(alignof_if_defined_else_default >= 4, "Result reserves two bits for tagging pointers"); Result(T* success); Result(E* error); Result(Result&& other); Result& operator=(Result&& other); ~Result(); bool IsError() const; bool IsSuccess() const; T* AcquireSuccess(); E* AcquireError(); private: enum PayloadType { Success = 0, Error = 1, Empty = 2, }; // Utility functions to manipulate the tagged pointer. Some of them don't need to be templated // but we really want them inlined so we keep them in the headers static intptr_t MakePayload(void* pointer, PayloadType type); static PayloadType GetPayloadType(intptr_t payload); static T* GetSuccessFromPayload(intptr_t payload); static E* GetErrorFromPayload(intptr_t payload); constexpr static intptr_t kEmptyPayload = Empty; intptr_t mPayload = kEmptyPayload; }; // Catchall definition of Result implemented as a tagged struct. It could be improved to use // a tagged union instead if it turns out to be a hotspot. T and E must be movable and default // constructible. template class DAWN_NO_DISCARD Result { public: Result(T&& success); Result(E&& error); Result(Result&& other); Result& operator=(Result&& other); ~Result(); bool IsError() const; bool IsSuccess() const; T&& AcquireSuccess(); E&& AcquireError(); private: enum PayloadType { Success = 0, Error = 1, Acquired = 2, }; PayloadType mType; E mError; T mSuccess; }; // Implementation of Result template Result::Result() { } template Result::Result(E* error) : mError(error) { } template Result::Result(Result&& other) : mError(other.mError) { other.mError = nullptr; } template Result& Result::operator=(Result&& other) { ASSERT(mError == nullptr); mError = other.mError; other.mError = nullptr; return *this; } template Result::~Result() { ASSERT(mError == nullptr); } template bool Result::IsError() const { return mError != nullptr; } template bool Result::IsSuccess() const { return mError == nullptr; } template void Result::AcquireSuccess() { } template E* Result::AcquireError() { E* error = mError; mError = nullptr; return error; } // Implementation of Result template Result::Result(T* success) : mPayload(MakePayload(success, Success)) { } template Result::Result(E* error) : mPayload(MakePayload(error, Error)) { } template Result::Result(Result&& other) : mPayload(other.mPayload) { other.mPayload = kEmptyPayload; } template Result& Result::operator=(Result&& other) { ASSERT(mPayload == kEmptyPayload); mPayload = other.mPayload; other.mPayload = kEmptyPayload; return *this; } template Result::~Result() { ASSERT(mPayload == kEmptyPayload); } template bool Result::IsError() const { return GetPayloadType(mPayload) == Error; } template bool Result::IsSuccess() const { return GetPayloadType(mPayload) == Success; } template T* Result::AcquireSuccess() { T* success = GetSuccessFromPayload(mPayload); mPayload = kEmptyPayload; return success; } template E* Result::AcquireError() { E* error = GetErrorFromPayload(mPayload); mPayload = kEmptyPayload; return error; } template intptr_t Result::MakePayload(void* pointer, PayloadType type) { intptr_t payload = reinterpret_cast(pointer); ASSERT((payload & 3) == 0); return payload | type; } template typename Result::PayloadType Result::GetPayloadType(intptr_t payload) { return static_cast(payload & 3); } template T* Result::GetSuccessFromPayload(intptr_t payload) { ASSERT(GetPayloadType(payload) == Success); return reinterpret_cast(payload); } template E* Result::GetErrorFromPayload(intptr_t payload) { ASSERT(GetPayloadType(payload) == Error); return reinterpret_cast(payload ^ 1); } // Implementation of Result template Result::Result(T&& success) : mType(Success), mSuccess(std::move(success)) { } template Result::Result(E&& error) : mType(Error), mError(std::move(error)) { } template Result::~Result() { ASSERT(mType == Acquired); } template Result::Result(Result&& other) : mType(other.mType), mError(std::move(other.mError)), mSuccess(std::move(other.mSuccess)) { other.mType = Acquired; } template Result& Result::operator=(Result&& other) { mType = other.mType; mError = std::move(other.mError); mSuccess = std::move(other.mSuccess); other.mType = Acquired; return *this; } template bool Result::IsError() const { return mType == Error; } template bool Result::IsSuccess() const { return mType == Success; } template T&& Result::AcquireSuccess() { ASSERT(mType == Success); mType = Acquired; return std::move(mSuccess); } template E&& Result::AcquireError() { ASSERT(mType == Error); mType = Acquired; return std::move(mError); } #endif // COMMON_RESULT_H_