OpenCyphal-Garage / libcyphal

Portable reference implementation of the Cyphal protocol stack in C++ for embedded systems and Linux.
http://opencyphal.org
MIT License
292 stars 502 forks source link

Use cetl::unbounded_variant for errors originating from the layers below LibCyphal #345

Closed pavel-kirienko closed 3 months ago

pavel-kirienko commented 5 months ago

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>>;

ImplementationCell is a wrapper over unbounded_variant or any 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

thirtytwobits commented 5 months ago

Option 2 ❤️

pavel-kirienko commented 3 months ago

Done in #361