Currently, the error type of librbd is int, which contains an error code of system_error
category.
Each function uses their own meaning for different error codes,
also a code can be used for different situations,
e.g. PreRemoveRequest returns EBUSY when an image has watchers,
on acquiring a lock, or when the image is being migrated.
Librados uses the same int as an error type,
however neorados uses boost::system::error_code.
Latest version of boost::system::error_code is {std::error_code, std::source_location*}.
And std::error_code is {int, error_category*} (in stdlibc++ and libcxx).
It is possible to use custom error types, internally,
however user-facing API have to use a popular type such as std or boost error_code,
otherwise users would have to convert error types.
As std::error_code is the standard error type, and boost::system::error_code is
convertible to it
Various alternative approaches to errors, such as Boost.LEAF,
Boost.Outcome and the corresponding std::error p1028 proposal,
all of them are compatible with std(boost)::error_code.
As std::error_code is a pair of an int and a pointer, it fits into two registers
(eax:rdx),
so changing result type from int to std::error_code basically adds one instruction:
std::error_code f() {
return SomeErrEnum::Code1;
}
with properly defined std::is_error_code_enum<SomeErrEnum> trait,
and a SomeErrCategory type with a corresponding error category global value,
compiles to
f():
mov edx, OFFSET some_category_var
mov eax, 1
ret
Full code -
https://godbolt.org/z/K7vv5h8oo
Although there are corner cases such as surprisingly ineffective
error_category& cat() { static SomeErrCategory c; return c; }
where "static" requires branching for a guard variable,
it is possible to keep overhead to adding single instruction for the error category.
Back to the librbd.
It is indeed not possible to simply change it's C++ API from returning int to
error_code.
Also, we cannot add overloads with different return types, we'd have to also change
arguments.
Passing error_code as an output parameter works
int flatten(); // current API
void flatten(std::error_code& ec); // alternative API
but it doesn't look great, ideally we'd rather use
"std::expected<T, error_code> f()" and not
"void f(T& out, error_code& ec)".
We can also add asynchronous API like in neorados,
with CompletionTokens and callbacks that receive error_code.
However, much of librbd code is synchronous (Operations),
so rewriting it to an async code would needlessly complicate things.
Is there a way to make librbd return std::error_code,
so that we could use different error categories, enums, for more distinct error codes?