rich-err 0.1.0

A highly detailed error type for compilers, tracebacks, etc.
Documentation
/// The type returned by [`ToErrorCode::code`].
pub type ErrorCode = u16;

/// Provides a non-ambiguous way to retrieve the [error code](ErrorCode) associated with a given
/// error.
///
/// An error code is meant to be a precise ID that helps users locate detailed documentation of the
/// error.
pub trait ToErrorCode {
    /// Returns the [error code](ErrorCode) associated with this error.
    ///
    /// If an application only requires one error type (and that type happens to be a unit `enum`),
    /// an implementation like this might be sufficient:
    ///
    /// ```rust
    /// # use rich_err::error_code::{ErrorCode, ToErrorCode};
    /// #[derive(Clone, Copy)]
    /// pub enum MyError {
    ///     Foo,
    ///     Bar,
    ///     Baz,
    /// }
    ///
    /// impl ToErrorCode for MyError {
    ///     fn code(&self) -> ErrorCode {
    ///         *self as ErrorCode
    ///     }
    /// }
    /// ```
    ///
    /// For more complex situations, it may make more sense to give each error type its own "prefix"
    /// and add that to the discriminant.
    ///
    /// ```rust
    /// # use rich_err::error_code::{ErrorCode, ToErrorCode};
    /// #[derive(Clone, Copy)]
    /// pub enum ErrorA {
    ///     Foo,
    ///     Bar,
    ///     Baz,
    /// }
    ///
    /// #[derive(Clone, Copy)]
    /// pub enum ErrorB {
    ///     Qux,
    ///     Quux,
    /// }
    ///
    /// #[derive(Clone, Copy)]
    /// pub enum ErrorC {
    ///     Fizz,
    ///     Buzz,
    /// }
    ///
    /// impl ToErrorCode for ErrorA {
    ///     fn code(&self) -> ErrorCode {
    ///         const PREFIX: ErrorCode = 0;
    ///         PREFIX + *self as ErrorCode
    ///     }
    /// }
    ///
    /// impl ToErrorCode for ErrorB {
    ///     fn code(&self) -> ErrorCode {
    ///         const PREFIX: ErrorCode = 100;
    ///         PREFIX + *self as ErrorCode
    ///     }
    /// }
    ///
    /// impl ToErrorCode for ErrorC {
    ///     fn code(&self) -> ErrorCode {
    ///         const PREFIX: ErrorCode = 200;
    ///         PREFIX + *self as ErrorCode
    ///     }
    /// }
    /// ```
    ///
    /// With this approach, the prefixes must be large enough to accomodate all error variants in
    /// each type. Otherwise, multiple errors might have the same [error code](ErrorCode).
    ///
    /// The previous two approaches rely on errors being unit `enum`s. Although this can work in
    /// some situations, it is not uncommon for error types to have tuple and/or struct variants as
    /// well. In these situations, it is still possible (albeit more difficult) to automatically
    /// retrieve the discriminant if the `enum` uses a [primitive representation]:
    ///
    /// ```rust
    /// # use rich_err::error_code::{ErrorCode, ToErrorCode};
    /// #[derive(Clone, Copy)]
    /// #[repr(u16)] // `ErrorCode` is two bytes wide.
    /// pub enum MyError {
    ///     Foo,
    ///     Bar(bool),
    ///     Baz {
    ///         something: f32,
    ///     }
    /// }
    ///
    /// impl ToErrorCode for MyError {
    ///     fn code(&self) -> ErrorCode {
    ///         unsafe { *<*const _>::from(self).cast::<ErrorCode>() }
    ///     }
    /// }
    /// ```
    ///
    /// This approach is compatible with the prefixes from before, but examples will be omitted for
    /// the sake of brevity.
    ///
    /// For more details regarding discriminants and safety, see the documentation for
    /// [`core::mem::discriminant`].
    ///
    /// [primitive representation]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations
    fn code(&self) -> ErrorCode;
}