Skip to content

Commit

Permalink
add doctest and FAQ
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed Jun 16, 2021
1 parent f1988eb commit d19e74f
Showing 1 changed file with 129 additions and 10 deletions.
139 changes: 129 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
//!
//! ## Example
//!
//! *Demonstration alongside the [`Error`][std::error::Error] derive macro from [`thiserror`](https://docs.rs/thiserror/1.0.25/thiserror/index.html),
//! to propagate source locations from [`io::Error`][std::io::Error] with the `#[source]` attribute:*
//! ```rust
//! use std::io;
//! use displaydoc::Display;
//! use thiserror::Error;
//!
//! #[derive(Display, Error, Debug)]
//! pub enum DataStoreError {
//! /// data store disconnected
//! /// data store disconnected: {0:?}
//! Disconnect(#[source] io::Error),
//! /// the data for key `{0}` is not available
//! Redaction(String),
Expand All @@ -33,32 +35,43 @@
//! /// unknown data store error
//! Unknown,
//! }
//!
//! let error = DataStoreError::Redaction("MK-ULTRA".to_string());
//! assert!("the data for key `MK-ULTRA` is not available" == &format!("{}", error));
//! ```
//!
//! <br>
//!
//! ## Details
//!
//! - A `Display` impl is generated for your type if you provide doc comment
//! messages on the struct or each variant of your enum, as shown above in the
//! example.
//!
//! The messages support a shorthand for interpolating fields from the error.
//!
//! - An `fmt::Display` impl is generated for your enum if you provide
//! a docstring comment on each variant as shown above in the example. The
//! `Display` derive macro supports a shorthand for interpolating fields from
//! the error:
//! - `/// {var}` ⟶ `write!("{}", self.var)`
//! - `/// {0}` ⟶ `write!("{}", self.0)`
//! - `/// {var:?}` ⟶ `write!("{:?}", self.var)`
//! - `/// {0:?}` ⟶ `write!("{:?}", self.0)`
//! - This also works with structs and [generic types][crate::Display#generic-type-parameters]:
//! ```rust
//! # use displaydoc::Display;
//! /// oh no, an error: {0}
//! #[derive(Display)]
//! pub struct Error<E>(pub E);
//!
//! let error: Error<&str> = Error("muahaha i am an error");
//! assert!("oh no, an error: muahaha i am an error" == &format!("{}", error));
//! ```
//!
//! <br>
//!
//! ## FAQ
//!
//! 1. **Is this crate `no_std` compatible?**
//! * Yes! This crate implements the `core::fmt::Display` trait not the `std::fmt::Display` trait so it should work in `std` and `no_std` environments. Just add `default-features = false`.
//! * Yes! This crate implements the [`core::fmt::Display`] trait, not the [`std::fmt::Display`] trait, so it should work in `std` and `no_std` environments. Just add `default-features = false`.
//!
//! 2. **Does this crate work with `Path` and `PathBuf` via the `Display` trait?**
//! * Yuuup. This crate uses @dtolnay's [autoref specialization technique](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) to add a special trait for types to get the display impl, it then specializes for `Path` and `PathBuf` and when either of these types are found it calls `self.display()` to get a `std::path::Display<'_>` type which can be used with the Display format specifier!
//! * Yuuup. This crate uses @dtolnay's [autoref specialization technique](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) to add a special trait for types to get the display impl. It then specializes for `Path` and `PathBuf`, and when either of these types are found, it calls `self.display()` to get a `std::path::Display<'_>` type which can be used with the `Display` format specifier!
#![doc(html_root_url = "https://docs.rs/displaydoc/0.2.1")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(
Expand Down Expand Up @@ -93,7 +106,113 @@ mod fmt;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

/// Derive macro for implementing `Display` via doc comment attributes
/// [Custom `#[derive(...)]` macro](https://doc.rust-lang.org/edition-guide/rust-2018/macros/custom-derive.html)
/// for implementing [`fmt::Display`][core::fmt::Display] via doc comment attributes.
///
/// ### Generic Type Parameters
///
/// Type parameters to an enum or struct using this macro should *not* need to
/// have an explicit `Display` constraint at the struct or enum definition
/// site. A `Display` implementation for the `derive`d struct or enum is
/// generated assuming each type parameter implements `Display`, but that should
/// be possible without adding the constraint to the struct definition itself:
/// ```rust
/// # use displaydoc::Display;
/// /// oh no, an error: {0}
/// #[derive(Display)]
/// pub struct Error<E>(pub E);
///
/// // No need to require `E: Display` above.
/// let error: Error<&str> = Error("muahaha i am an error");
/// assert!("oh no, an error: muahaha i am an error" == &format!("{}", error));
/// ```
///
/// ### Using [`Debug`][core::fmt::Debug] Implementations with Type Parameters
/// However, if a type parameter must instead be constrained with the
/// [`Debug`][core::fmt::Debug] trait so that some field may be printed with
/// `{:?}`, that constraint must currently still also be specified redundantly
/// at the struct or enum definition site. If a struct or enum field is being
/// formatted with `{:?}` via [`displaydoc`][crate], and a generic type
/// parameter must implement `Debug` to do that, then that struct or enum
/// definition will need to propagate the `Debug` constraint to every type
/// parameter it's instantiated with. This can become a problem when writing
/// generic code:
/// ```compile_fail
/// use core::fmt::Debug;
/// use displaydoc::Display;
///
/// #[derive(Debug)]
/// pub struct MyType;
///
/// // This docstring uses the `Debug` format specifier ":?":
/// /// oh no, an error: {0:?}
/// #[derive(Display)]
/// pub struct Error<E: Debug>(pub E);
///
/// // Error: the trait `std::fmt::Display` is not implemented for `MyType`.
/// assert!("oh no, an error: MyType" == &format!("{}", Error(MyType)));
/// ```
/// If this is a problem for you, it is recommended to instead:
/// 1. require [`fmt::Display`][core::fmt::Display] for any desired
/// parameterized type,
/// 2. use the `{}` format specifier for the struct or enum field, and
/// 3. make use of the workaround described in
/// [Generic Type Parameters][crate#generic-type-parameters] to avoid pinning
/// specifically the `fmt::Display` trait at the definition site.
///
/// Consumers of your generic API should be able to use `displaydoc::Display` to
/// implement `fmt::Display` for the concrete types they provide to your
/// parameterized errors without much boilerplate.
///
/// #### Trick the Macro to Avoid Requiring Everything to `Display` to `Display`
/// However, there may be cases where some type parameter can't be made to
/// implement `fmt::Display` or can't use the `displaydoc::Display` derive macro
/// for some reason. For example, this won't work immediately:
/// ```compile_fail
/// use core::fmt::Debug;
/// use displaydoc::Display;
///
/// #[derive(Debug)]
/// pub struct MyType;
///
/// /// oh no, an error: {0:?}
/// #[derive(Display)]
/// pub struct Error<E: Debug>(pub E);
///
/// // Still fails to compile because `displaydoc`'s `fmt::Display` implementation
/// // assumes all type parameters need to require `fmt::Display` themselves:
/// assert!("oh no, an error: MyType" == &format!("{}", Error(MyType)));
/// ```
/// We would like to be able to implement our own `Display` for any struct or
/// enum without requiring the type parameter to itself implement `Display`. In
/// such cases, the macro can be misled away from adding a `where E: Display`
/// requirement to its generated `fmt::Display` implementation by creating
/// a local trait *named* "Display". This allows the above example to
/// successfully compile:
/// ```rust
/// use core::fmt::Debug;
/// use displaydoc::Display;
///
/// #[derive(Debug)]
/// pub struct MyType;
///
/// mod inner {
/// use core::fmt::Debug;
/// // `#[derive(Display)]` checks for any constraint named "Display" and will
/// // avoid placing a `where E: core::fmt::Display` on that type param if it
/// // sees one.
/// pub trait Display: Debug {}
/// // This means the type is basically `Error<E: Debug>` though.
/// impl<E> Display for E where E: Debug {}
/// }
///
/// /// oh no, an error: {0:?}
/// #[derive(Display)]
/// pub struct Error<E: inner::Display>(pub E);
///
/// // MyType still does not itself need to impl `Display` at all!
/// assert!("oh no, an error: MyType" == &format!("{}", Error(MyType)));
/// ```
#[proc_macro_derive(Display, attributes(ignore_extra_doc_attributes))]
pub fn derive_error(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
Expand Down

0 comments on commit d19e74f

Please sign in to comment.