/* Mathieu Stefani, 03 mai 2016 This header provides a Result type that can be used to replace exceptions in code that has to handle error. Result can be used to return and propagate an error to the caller. Result is an algebraic data type that can either Ok(T) to represent success or Err(E) to represent an error. */ #pragma once #include #include #include namespace types { template struct Ok { Ok(const T& val) : val(val) { } Ok(T&& val) : val(std::move(val)) { } T val; }; template<> struct Ok { }; template struct Err { Err(const E& val) : val(val) { } Err(E&& val) : val(std::move(val)) { } E val; }; } template::type> types::Ok Ok(T&& val) { return types::Ok(std::forward(val)); } inline types::Ok Ok() { return types::Ok(); } template::type> types::Err Err(E&& val) { return types::Err(std::forward(val)); } namespace Rust { template struct Result; } namespace details { template struct void_t { typedef void type; }; namespace impl { template struct result_of; template struct result_of : public result_of { }; template struct result_of { typedef Ret type; }; } template struct result_of : public impl::result_of { }; template struct result_of { typedef Ret type; }; template struct result_of { typedef Ret type; }; template struct ResultOkType { typedef typename std::decay::type type; }; template struct ResultOkType> { typedef T type; }; template struct ResultErrType { typedef R type; }; template struct ResultErrType> { typedef typename std::remove_reference::type type; }; template struct IsResult : public std::false_type { }; template struct IsResult> : public std::true_type { }; namespace ok { namespace impl { template struct Map; template struct Map : public Map { }; template struct Map : public Map { }; // General implementation template struct Map { static_assert(!IsResult::value, "Can not map a callback returning a Result, use andThen instead"); template static Rust::Result map(const Rust::Result& result, Func func) { static_assert( std::is_same::value || std::is_convertible::value, "Incompatible types detected"); if (result.isOk()) { auto res = func(result.storage().template get()); return types::Ok(std::move(res)); } return types::Err(result.storage().template get()); } }; // Specialization for callback returning void template struct Map { template static Rust::Result map(const Rust::Result& result, Func func) { if (result.isOk()) { func(result.storage().template get()); return types::Ok(); } return types::Err(result.storage().template get()); } }; // Specialization for a void Result template struct Map { template static Rust::Result map(const Rust::Result& result, Func func) { static_assert(std::is_same::value, "Can not map a void callback on a non-void Result"); if (result.isOk()) { auto ret = func(); return types::Ok(std::move(ret)); } return types::Err(result.storage().template get()); } }; // Specialization for callback returning void on a void Result template<> struct Map { template static Rust::Result map(const Rust::Result& result, Func func) { static_assert(std::is_same::value, "Can not map a void callback on a non-void Result"); if (result.isOk()) { func(); return types::Ok(); } return types::Err(result.storage().template get()); } }; // General specialization for a callback returning a Result template struct Map (Arg)> { template static Rust::Result map(const Rust::Result& result, Func func) { static_assert( std::is_same::value || std::is_convertible::value, "Incompatible types detected"); if (result.isOk()) { auto res = func(result.storage().template get()); return res; } return types::Err(result.storage().template get()); } }; // Specialization for a void callback returning a Result template struct Map (void)> { template static Rust::Result map(const Rust::Result& result, Func func) { static_assert(std::is_same::value, "Can not call a void-callback on a non-void Result"); if (result.isOk()) { auto res = func(); return res; } return types::Err(result.storage().template get()); } }; } // namespace impl template struct Map : public impl::Map { }; template struct Map : public impl::Map { }; template struct Map : public impl::Map { }; template struct Map : public impl::Map { }; template struct Map> : public impl::Map { }; } // namespace ok namespace err { namespace impl { template struct Map; template struct Map { static_assert(!IsResult::value, "Can not map a callback returning a Result, use orElse instead"); template static Rust::Result map(const Rust::Result& result, Func func) { if (result.isErr()) { auto res = func(result.storage().template get()); return types::Err(res); } return types::Ok(result.storage().template get()); } template static Rust::Result map(const Rust::Result& result, Func func) { if (result.isErr()) { auto res = func(result.storage().template get()); return types::Err(res); } return types::Ok(); } }; } // namespace impl template struct Map : public impl::Map { }; } // namespace err; namespace And { namespace impl { template struct Then; template struct Then : public Then { }; template struct Then : public Then { }; template struct Then : public Then { }; template struct Then { static_assert(std::is_same::value, "then() should not return anything, use map() instead"); template static Rust::Result then(const Rust::Result& result, Func func) { if (result.isOk()) { func(result.storage().template get()); } return result; } }; template struct Then { static_assert(std::is_same::value, "then() should not return anything, use map() instead"); template static Rust::Result then(const Rust::Result& result, Func func) { static_assert(std::is_same::value, "Can not call a void-callback on a non-void Result"); if (result.isOk()) { func(); } return result; } }; } // namespace impl template struct Then : public impl::Then { }; template struct Then : public impl::Then { }; template struct Then : public impl::Then { }; template struct Then : public impl::Then { }; } // namespace And namespace Or { namespace impl { template struct Else; template struct Else : public Else { }; template struct Else : public Else { }; template struct Else : public Else { }; template struct Else (Arg)> { template static Rust::Result orElse(const Rust::Result& result, Func func) { static_assert( std::is_same::value || std::is_convertible::value, "Incompatible types detected"); if (result.isErr()) { auto res = func(result.storage().template get()); return res; } return types::Ok(result.storage().template get()); } template static Rust::Result orElse(const Rust::Result& result, Func func) { if (result.isErr()) { auto res = func(result.storage().template get()); return res; } return types::Ok(); } }; template struct Else (void)> { template static Rust::Result orElse(const Rust::Result& result, Func func) { static_assert(std::is_same::value, "Can not call a void-callback on a non-void Result"); if (result.isErr()) { auto res = func(); return res; } return types::Ok(result.storage().template get()); } template static Rust::Result orElse(const Rust::Result& result, Func func) { if (result.isErr()) { auto res = func(); return res; } return types::Ok(); } }; } // namespace impl template struct Else : public impl::Else { }; template struct Else : public impl::Else { }; template struct Else : public impl::Else { }; template struct Else : public impl::Else { }; } // namespace Or namespace Other { namespace impl { template struct Wise; template struct Wise : public Wise { }; template struct Wise : public Wise { }; template struct Wise : public Wise { }; template struct Wise { template static Rust::Result otherwise(const Rust::Result& result, Func func) { static_assert( std::is_same::value || std::is_convertible::value, "Incompatible types detected"); static_assert(std::is_same::value, "callback should not return anything, use mapError() for that"); if (result.isErr()) { func(result.storage().template get()); } return result; } }; } // namespace impl template struct Wise : public impl::Wise { }; template struct Wise : public impl::Wise { }; template struct Wise : public impl::Wise { }; template struct Wise : public impl::Wise { }; } // namespace Other template::type >::type, E> > Ret map(const Rust::Result& result, Func func) { return ok::Map::map(result, func); } template::type >::type > > Ret mapError(const Rust::Result& result, Func func) { return err::Map::map(result, func); } template Rust::Result then(const Rust::Result& result, Func func) { return And::Then::then(result, func); } template Rust::Result otherwise(const Rust::Result& result, Func func) { return Other::Wise::otherwise(result, func); } template::type >::type > > Ret orElse(const Rust::Result& result, Func func) { return Or::Else::orElse(result, func); } struct ok_tag { }; struct err_tag { }; template struct Storage { static constexpr size_t Size = sizeof(T) > sizeof(E) ? sizeof(T) : sizeof(E); static constexpr size_t Align = sizeof(T) > sizeof(E) ? alignof(T) : alignof(E); typedef typename std::aligned_storage::type type; Storage() : initialized_(false) { } void construct(types::Ok ok) { new (&storage_) T(ok.val); initialized_ = true; } void construct(types::Err err) { new (&storage_) E(err.val); initialized_ = true; } template void rawConstruct(U&& val) { typedef typename std::decay::type CleanU; new (&storage_) CleanU(std::forward(val)); initialized_ = true; } template const U& get() const { return *reinterpret_cast(&storage_); } template U& get() { return *reinterpret_cast(&storage_); } void destroy(ok_tag) { if (initialized_) { get().~T(); initialized_ = false; } } void destroy(err_tag) { if (initialized_) { get().~E(); initialized_ = false; } } type storage_; bool initialized_; }; template struct Storage { typedef typename std::aligned_storage::type type; void construct(types::Ok) { initialized_ = true; } void construct(types::Err err) { new (&storage_) E(err.val); initialized_ = true; } template void rawConstruct(U&& val) { typedef typename std::decay::type CleanU; new (&storage_) CleanU(std::forward(val)); initialized_ = true; } void destroy(ok_tag) { initialized_ = false; } void destroy(err_tag) { if (initialized_) { get().~E(); initialized_ = false; } } template const U& get() const { return *reinterpret_cast(&storage_); } template U& get() { return *reinterpret_cast(&storage_); } type storage_; bool initialized_; }; template struct Constructor { static void move(Storage&& src, Storage& dst, ok_tag) { dst.rawConstruct(std::move(src.template get())); src.destroy(ok_tag()); } static void copy(const Storage& src, Storage& dst, ok_tag) { dst.rawConstruct(src.template get()); } static void move(Storage&& src, Storage& dst, err_tag) { dst.rawConstruct(std::move(src.template get())); src.destroy(err_tag()); } static void copy(const Storage& src, Storage& dst, err_tag) { dst.rawConstruct(src.template get()); } }; template struct Constructor { static void move(Storage&& src, Storage& dst, ok_tag) { } static void copy(const Storage& src, Storage& dst, ok_tag) { } static void move(Storage&& src, Storage& dst, err_tag) { dst.rawConstruct(std::move(src.template get())); src.destroy(err_tag()); } static void copy(const Storage& src, Storage& dst, err_tag) { dst.rawConstruct(src.template get()); } }; } // namespace details namespace rpog { template struct EqualityComparable : std::false_type { }; template struct EqualityComparable() == std::declval())>::type >::type > : std::true_type { }; } // namespace rpog namespace Rust { template struct Result { static_assert(!std::is_same::value, "void error type is not allowed"); typedef details::Storage storage_type; Result(types::Ok ok) : ok_(true) { storage_.construct(std::move(ok)); } Result(types::Err err) : ok_(false) { storage_.construct(std::move(err)); } Result(Result&& other) { if (other.isOk()) { details::Constructor::move(std::move(other.storage_), storage_, details::ok_tag()); ok_ = true; } else { details::Constructor::move(std::move(other.storage_), storage_, details::err_tag()); ok_ = false; } } Result(const Result& other) { if (other.isOk()) { details::Constructor::copy(other.storage_, storage_, details::ok_tag()); ok_ = true; } else { details::Constructor::copy(other.storage_, storage_, details::err_tag()); ok_ = false; } } ~Result() { if (ok_) storage_.destroy(details::ok_tag()); else storage_.destroy(details::err_tag()); } bool isOk() const { return ok_; } bool isErr() const { return !ok_; } T expect(const char* str) const { if (!isOk()) { std::fprintf(stderr, "%s\n", str); std::terminate(); } return expect_impl(std::is_same()); } template::type >::type, E> > Ret map(Func func) const { return details::map(*this, func); } template::type >::type > > Ret mapError(Func func) const { return details::mapError(*this, func); } template Result then(Func func) const { return details::then(*this, func); } template Result otherwise(Func func) const { return details::otherwise(*this, func); } template::type >::type > > Ret orElse(Func func) const { return details::orElse(*this, func); } storage_type& storage() { return storage_; } const storage_type& storage() const { return storage_; } template typename std::enable_if< !std::is_same::value, U >::type unwrapOr(const U& defaultValue) const { if (isOk()) { return storage().template get(); } return defaultValue; } template typename std::enable_if< !std::is_same::value, U >::type unwrap() const { if (isOk()) { return storage().template get(); } std::fprintf(stderr, "Attempting to unwrap an error Result\n"); std::terminate(); } E unwrapErr() const { if (isErr()) { return storage().template get(); } std::fprintf(stderr, "Attempting to unwrapErr an ok Result\n"); std::terminate(); } private: T expect_impl(std::true_type) const { } T expect_impl(std::false_type) const { return storage_.template get(); } bool ok_; storage_type storage_; }; template bool operator==(const Rust::Result& lhs, const Rust::Result& rhs) { static_assert(rpog::EqualityComparable::value, "T must be EqualityComparable for Result to be comparable"); static_assert(rpog::EqualityComparable::value, "E must be EqualityComparable for Result to be comparable"); if (lhs.isOk() && rhs.isOk()) { return lhs.storage().template get() == rhs.storage().template get(); } if (lhs.isErr() && rhs.isErr()) { return lhs.storage().template get() == rhs.storage().template get(); } } template bool operator==(const Rust::Result& lhs, types::Ok ok) { static_assert(rpog::EqualityComparable::value, "T must be EqualityComparable for Result to be comparable"); if (!lhs.isOk()) return false; return lhs.storage().template get() == ok.val; } template bool operator==(const Rust::Result& lhs, types::Ok) { return lhs.isOk(); } template bool operator==(const Rust::Result& lhs, types::Err err) { static_assert(rpog::EqualityComparable::value, "E must be EqualityComparable for Result to be comparable"); if (!lhs.isErr()) return false; return lhs.storage().template get() == err.val; } } // end namespace Rust #define TRY(...) \ ({ \ auto res = __VA_ARGS__; \ if (!res.isOk()) { \ typedef details::ResultErrType::type E; \ return types::Err(res.storage().get()); \ } \ typedef details::ResultOkType::type T; \ res.storage().get(); \ })