You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Support Option as Error::source() in derive(Error) (#459, #426)
## Synopsis
See #426 (comment):
> I’d like to be able to use `derive_more` with optional sources, like:
> ```rust
> #[derive(derive_more::Error, derive_more::Display, Debug)]
> #[display("it’s an error")]
> struct MyErr {
> source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
> }
> ```
## Solution
The way, the `thiserror` does it, is by
[checking](https://docs.rs/thiserror-impl/2.0.12/src/thiserror_impl/expand.rs.html#50)
the [type name to contain
`Option`](https://docs.rs/thiserror-impl/2.0.12/src/thiserror_impl/expand.rs.html#559-583).
Relying on type could be fragile, as the call site could introduce its
own type named `Option` in the scope where macro is called. Ideally, we
need to aid this case with type machinery.
For supporting this via type machinery, we obviously need some sort of
specialization to specialize case `Option<E: Error>` over `E: Error`,
since vanilla Rust coherence naturally hits the wall of "upstream crates
may add a new impl of trait `std::error::Error` for type `Option<_>` in
future versions". The usual solution is to use [autoref-based
specialization](https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html)
as [we do for
`AsRef`](https://docs.rs/derive_more/2.0.1/src/derive_more/as.rs.html).
However, autoref-based specialization doesn't work in a generic context
(i.e. `MyError<E1, E2>`), since it requires trait bounds to work, and we
cannot describe conditional trait bounds in Rust, which leads to the
situation that this case could not be supported at all:
```rust
struct MyError<E> {
source: Option<E>,
}
```
So, choosing between checking the field's type syntactically (and so
loosing support for renamed types) and not supporting optional `source`
in generic contexts, I would definitely choose to support generics
despite having naming issues in edge cases. As the result, this PR
implements the similar syntactically-based solution as the `thiserror`
crate does. The implications of this are warned and described in the
documentation.
## Future possibilities
Additionally, we may support `#[error(source(optional))]` attribute,
which will allow using renamed types naturally:
```rust
use derive_more::{Display, Error};
#[derive(Debug, Display, Error)]
struct Simple;
#[derive(Debug, Display, Error)]
#[display("Oops!")]
struct Generic(#[error(source(optional))] RenamedOption<Simple>); // froces recognizing as `Option`
type RenamedOption<T> = Option<T>;
```
However, it's better to be implemented as a separate PR, to not
overcomplicate this one.
0 commit comments