A classic approach here would be to use inheritance directly, allowing the platform layer to define arbitrary error types derived from our PlatformError, like this.
However, it is impossible to return a polymorphic object by value directly when its runtime type is unknown; we need either heap (which is not an option) or a type-erased container like cetl::unbounded_variant (see inheritance without pointers).
We define a small set of well-known error types that can occur within the libcyphal middleware itself, such as ArgumentError, MemoryError, etc, and assume them to be rigid and non-extensible. Other error types are used to model errors originating from the lower layers, and they necessarily have to be arbitrarily extensible. We have several approaches here:
1. Use an opaque container:
using PlatformError = cetl::unbounded_variant<Footprint>;
//...
using AnyError = cetl::variant<ArgumentError, MemoryError, ..., PlatformError>;
This is straightforward, but one downside is that it may provide too much flexibility for the platform layer. For example, one could push an int into the opaque container, which is not a sensible approach to error handling.
2. Use by-value polymorphism:
class IPlatformError : public cetl::rtti_helper<...> // rtti_helper can be avoided here
{
protected:
IPlatformError() = default;
virtual ~IPlatformError() = default;
};
using PlatformError = ImplementationCell<IPlatformError, cetl::unbounded_variant<Footprint>>;
This solution restricts the set of error types that can be returned from the platform layer and allows us to define arbitrary polymorphic methods on them, like what.
A classic approach here would be to use inheritance directly, allowing the platform layer to define arbitrary error types derived from our
PlatformError
, like this.However, it is impossible to return a polymorphic object by value directly when its runtime type is unknown; we need either heap (which is not an option) or a type-erased container like
cetl::unbounded_variant
(see inheritance without pointers).We define a small set of well-known error types that can occur within the libcyphal middleware itself, such as
ArgumentError
,MemoryError
, etc, and assume them to be rigid and non-extensible. Other error types are used to model errors originating from the lower layers, and they necessarily have to be arbitrarily extensible. We have several approaches here:1. Use an opaque container:
This is straightforward, but one downside is that it may provide too much flexibility for the platform layer. For example, one could push an
int
into the opaque container, which is not a sensible approach to error handling.2. Use by-value polymorphism:
ImplementationCell
is a wrapper overunbounded_variant
orany
defined here: https://github.com/OpenCyphal-Garage/libcyphal/blob/main/docs/design/readme.md#type-erasure-without-pointers.This solution restricts the set of error types that can be returned from the platform layer and allows us to define arbitrary polymorphic methods on them, like
what
.@thirtytwobits please review