Closed zhscn closed 2 months ago
Mixins are always default constructed. The only way to set or get the value of a stateful mixin is via a member function in that mixin.
It seems to me what you're actually seeking is a custom payload type for your domain. For this you would need to define the domain manually, not using quick_status_code_from_enum
, setting the domain's value type to a type which has your enum plus detailed reason of failure. This is more typing, but it'll solve your problem.
If you'd like an example of this in practice, https://github.com/ned14/llfio/blob/develop/include/llfio/v2.0/status_code.hpp#L81 shows a custom file i/o error type which adds a log entry and other details to some status code value type. A mixin enables decoding of the additional data explaining the cause. We then rebind multiple types of status code into these enhanced status codes, and everything works very well.
Let me know if you need help understanding that example of use.
Thanks for the information! Follow the documentation of outcome I could write my custom status domain.
The only confusing thing is the mixin
in mixins
namespace in llfio example, why not just put these methods into the file_io_error_domain
directly? Could this mixin
become stateful?
Glad to hear you got it working.
A domain can be shared by many status code, so putting those per-code methods into the domain wouldn't be useful for most situations.
You can put the methods into the payload type, and sometimes that makes more sense than putting them into the status code.
Really it depends on what you're solving. It was considered that mixins could have a non-default constructor, but somebody pointed out that if you really, really want that you can always inherit from status code into a custom type and set the mixin's state from there. So it seemed redundant to make status code's constructors even more complicated.
I don't fully understand it, but it’s probably not what I need for now.
Finally, outcome is an awesome library help me get rid of the annoying error handing in daily programming. Thank you for the support!
You're very kind. And thank you for the sponsoring support.
Hi @ned14,
I recently read the source code for status_code
and refactored my custom domain. Here is my code:
#include <boost/outcome.hpp>
#include <boost/outcome/experimental/status-code/config.hpp>
#include <boost/outcome/experimental/status-code/generic_code.hpp>
#include <boost/outcome/experimental/status-code/nested_status_code.hpp>
#include <boost/outcome/experimental/status-code/quick_status_code_from_enum.hpp>
#include <boost/outcome/experimental/status-code/status_code.hpp>
#include <boost/outcome/experimental/status-code/status_code_domain.hpp>
#include <boost/outcome/experimental/status_outcome.hpp>
#include <boost/outcome/experimental/status_result.hpp>
#include <format>
#include <iostream>
#include <string>
#include <string_view>
namespace outcome = BOOST_OUTCOME_V2_NAMESPACE;
namespace outcome_e = outcome::experimental;
template <typename T>
using Result = outcome_e::status_result<T>;
template <>
struct std::formatter<outcome_e::error> : std::formatter<std::string_view> {
template <typename FormatContext>
auto format(const outcome_e::error& err, FormatContext& ctx) const {
auto msg = err.message();
auto msg_v = std::string_view{msg.data(), msg.size()};
return std::formatter<std::string_view>::format(msg_v, ctx);
}
};
enum class MyErrorEnum : int {
err0,
err1,
};
BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_BEGIN
template <>
struct quick_status_code_from_enum<::MyErrorEnum>
: quick_status_code_from_enum_defaults<::MyErrorEnum> {
static constexpr auto domain_name = "MyError";
static constexpr auto domain_uuid = "{6bf9e302-8ec0-4a23-a778-b39389075e8d}";
static constexpr auto domain_uuid2 = "{e4db5141-f378-47c4-9e84-5f83ff273835}";
static const std::initializer_list<mapping>& value_mappings() {
static const std::initializer_list<mapping> v{
{.value = ::MyErrorEnum::err0, .message = "err0", .code_mappings = {}},
{.value = ::MyErrorEnum::err1, .message = "err1", .code_mappings = {}},
};
return v;
}
};
BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_END
template <typename Enum>
struct EnumMessage {
Enum e;
std::string message;
};
template <typename Enum>
struct EnumMessageDomain;
template <typename Enum>
using EnumMessageError = outcome_e::status_code<EnumMessageDomain<Enum>>;
template <typename Enum>
struct EnumMessageDomain final : public outcome_e::status_code_domain {
template <class DomainType>
friend class status_code;
using Base = outcome_e::status_code_domain;
using Src = outcome_e::quick_status_code_from_enum<Enum>;
using value_type = EnumMessage<Enum>;
using Base::atomic_refcounted_string_ref;
using Base::payload_info_t;
using Base::string_ref;
constexpr EnumMessageDomain()
: Base(Src::domain_uuid2,
_uuid_size<outcome_e::detail::cstrlen(Src::domain_uuid2)>()) {}
EnumMessageDomain(const EnumMessageDomain&) = default;
EnumMessageDomain(EnumMessageDomain&&) = default;
EnumMessageDomain& operator=(const EnumMessageDomain&) = default;
EnumMessageDomain& operator=(EnumMessageDomain&&) = default;
~EnumMessageDomain() = default;
static constexpr const EnumMessageDomain& get();
string_ref name() const noexcept final {
return string_ref(Src::domain_name);
}
payload_info_t payload_info() const noexcept final {
return {sizeof(value_type),
sizeof(status_code_domain*) + sizeof(value_type),
(alignof(value_type) > alignof(status_code_domain*))
? alignof(value_type)
: alignof(status_code_domain*)};
}
static constexpr const typename Src::mapping* _find_mapping(Enum e) noexcept {
for (const auto& i : Src::value_mappings()) {
if (i.value == e) {
return &i;
}
}
return nullptr;
}
bool _do_failure(
const outcome_e::status_code<void>& code) const noexcept final {
assert(code.domain() == *this);
const auto& c = static_cast<const EnumMessageError<Enum>&>(code);
const auto* mapping = _find_mapping(c.value().e);
assert(mapping != nullptr);
if (mapping != nullptr) {
for (auto ec : mapping->code_mappings) {
if (ec == outcome_e::errc::success) {
return false;
}
}
}
return true;
}
bool _do_equivalent(
const outcome_e::status_code<void>& code1,
const outcome_e::status_code<void>& code2) const noexcept final {
assert(code1.domain() == *this);
const auto& c1 = static_cast<const EnumMessageError<Enum>&>(code1);
if (code2.domain() == *this) {
const auto& c2 = static_cast<const EnumMessageError<Enum>&>(code2);
return c1.value().e == c2.value().e;
}
if (code2.domain() ==
outcome_e::_quick_status_code_from_enum_domain<Enum>::get()) {
const auto& c2 =
static_cast<const outcome_e::quick_status_code_from_enum_code<Enum>&>(
code2);
return c1.value().e == c2.value();
}
if (code2.domain() == outcome_e::generic_code_domain) {
const auto& c2 = static_cast<const outcome_e::generic_code&>(code2);
const auto* mapping = _find_mapping(c1.value().e);
assert(mapping != nullptr);
if (mapping != nullptr) {
for (auto ec : mapping->code_mappings) {
if (ec == c2.value()) {
return true;
}
}
}
}
return false;
}
outcome_e::generic_code _generic_code(
const outcome_e::status_code<void>& code) const noexcept final {
assert(code.domain() == *this);
const auto* mapping = _find_mapping(
static_cast<const EnumMessageError<Enum>&>(code).value().e);
assert(mapping != nullptr);
if (mapping != nullptr) {
if (mapping->code_mappings.size() > 0) {
return *mapping->code_mappings.begin();
}
}
return outcome_e::errc::unknown;
}
string_ref _do_message(
const outcome_e::status_code<void>& code) const noexcept final {
assert(code.domain() == *this);
const EnumMessage<Enum>& c =
static_cast<const EnumMessageError<Enum>&>(code).value();
const auto mapping = _find_mapping(c.e);
auto s = std::format("{}: {}", mapping->message, c.message);
auto p = (char*)malloc(s.size());
memcpy(p, s.data(), s.size());
return atomic_refcounted_string_ref(p, s.size());
}
void _do_throw_exception(
const outcome_e::status_code<void>& code) const final;
};
template <class Enum>
constexpr EnumMessageDomain<Enum> enum_message_domain = {};
template <class Enum>
inline constexpr const EnumMessageDomain<Enum>& EnumMessageDomain<Enum>::get() {
return enum_message_domain<Enum>;
}
template <class Enum>
inline outcome_e::system_code make_status_code(EnumMessageError<Enum> v) {
return outcome_e::make_nested_status_code(std::move(v));
}
template <class Enum>
void EnumMessageDomain<Enum>::_do_throw_exception(
const outcome_e::status_code<void>& code) const {
assert(code.domain() == *this);
const auto& c = static_cast<const EnumMessageError<Enum>&>(code);
throw outcome_e::status_error<EnumMessageDomain<Enum>>(c);
}
using MyError = EnumMessageError<MyErrorEnum>;
Result<void> foo() {
return MyError({MyErrorEnum::err0, "Hello"});
}
Result<void> bar() {
return MyErrorEnum::err0;
}
void test() {
auto result = foo();
assert(result.has_error());
const auto& err = result.error();
std::cout << std::format("Error: {}\n", err);
if (MyErrorEnum::err0 == err) {
std::cout << "Compare err0\n";
}
if (err == MyErrorEnum::err1) {
std::cout << "Compare err1\n";
}
auto r = bar();
std::cout << (r.error() == err) << '\n'; // true
}
The EnumMessageDomain
is based on _quick_status_code_from_enum_domain
, and they seem to work well together. Could you provide me with some suggestions about this code?
From a quick inspection, it looks reasonable. Nothing obvious stands out.
Does it work as expected?
It works, seems that I have no problem understanding the code. If there is a chance later, I will try to introduce status_code to our project.
Excellent to hear!
My current employer uses it in a mixed C, C++ and Rust project. Works lovely. I wish it could enter the C++ standard library, but I am exhausted of WG21.
I wish it could enter the C++ standard library, but I am exhausted of WG21.
So sad hear about that. Hopes it could be a part of C++26.
and Rust project
I am very interested in this topic. Is it the C api described in https://ned14.github.io/outcome/experimental/c-api/ ?
More like https://ned14.github.io/outcome/experimental/c-api/from-c/ which has been recently added.
You can do a quick status code from enum in C now. You still need to link in a C++ helper runtime, but it works great.
C APIs are very easy to wrap into Rust using bindgen
. We wrote a bit of Rust shim code to make it easy to go between C Results and Rust Results and it works surprisingly well.
A useful tip is don't use the C macros directly! They are very long to type out. Instead, alias them into your C project using shorter names meaningful for your project. This saves your fingers!
These tips are very helpful!
In the past, I attempted to introduce Rust to our team, but the rest of the team wasn’t familiar with Rust, we finally didn’t move forward with it.
I might try it in my personal projects in the future.
Me personally, I think there are things Rust is great for, but it's not better than C++ for most things overall.
My current employer the internet facing code is in Rust. This makes sense, that's where attacks come from, and we close off lots of attack space by using Rust. The Rust layer hands off work to the C++ layer which does the work. Underneath both C++ and Rust is a C scheduling and i/o layer which figures out what work to do next for both C++ and Rust. C was chosen because Rust can speak C well, and C is surprisingly strong for writing schedulers and other 100% deterministic stuff.
I'd also use Rust for kernel device drivers personally. Anywhere where guaranteed lifetime safety is worth the other costs of using Rust.
For most other use cases, I'm not so keen on Rust. It has many non obvious costs especially in an evolving mature codebase, including finding suitably skilled developers.
I have a feeling I can't shake that there is a programming language better than C or C++ or Rust and is clearly better than any of those. No time to try designing something myself however.
I'm trying to understand the design of status_code and have encountered an issue with attaching the payload to the
mixin
:Here,
failure_context
represents a detailed reason for the failure, such as "the type of JSON member /path/to/member should be a number," rather than a rough message like "not_a_number" from the enum of status_code or error_code.The question here is how do I construct a concrete status_code using the above configuration?
The compiler complains
no matching constructor for initialization of 'outcome_e::status_result<int>'
Another related question is how can I extract the
failure_context
from the caller's side?