Suppose I have a type
struct ListNode<T> {
data: T,
next: Option<Box<Self>>,
}
and I want to parse this as a string, so I define the following error type
enum Error<T: FromStr> {
TooFewNodes,
Parse(<T as FromStr>::Err),
}
impl<T: FromStr> FromStr for ListNode<T> {
type Err = Error<T>;
// ...
}
Now, if I want to implement std::error::Error
on my error type, while retaining nostd compatibility, I can do so by
#[cfg(feature = "std")]
impl<T: std::error::Error> std::error::Error for Error<T> {
// ...
}
Ok, but now I want to get rid of the <T>
bound on the error type, because it results in multiple copies of the type (and every type that contains it) plus it's annoying to deal with. I can assume I have an allocator because otherwise none of the recursive types that are fundamental to my library can work. The standard way would be to change my error type to
enum Error {
TooFewNodes,
Parse(Box<dyn std::error::Error>),
}
Except that this type obviously can't be defined in a nostd environment. Ok, I can encapsulate the box inside an opaque type which conditionally holds either a dyn Error
or a dyn Display
, say. This type can exist in a nostd environment but I can't actually construct it in the way that I want! Ultimately what I want is
- If
std
is off, the type should always hold adyn Display
- If
std
is on, the type should hold adyn Error
(if the underlying error type implementsError
).
The problem, of course, is that anyone who tries to use a non-std::error::Error
error type with my library will find that my code compiles for them in nostd mode but not in std mode, i.e. my std
feature is not additive.
Judging from the lack of solutions in thiserror
and anyhow
where dtolnay (who has done more than anyone to abuse the language in the direction of making error traits usable) I assume this is just impossible. But this is a huge ergonomic hit, that I either need a <T>
parameter on pretty-much every error type in my library (and my consumers' libraries) or I need to throw away the error information from T
.