# derive-where [![Crates.io Version](https://img.shields.io/crates/v/derive-where.svg)](https://crates.io/crates/derive-where) [![Live Build Status](https://img.shields.io/github/actions/workflow/status/ModProg/derive-where/test.yml?branch=main)](https://github.com/ModProg/derive-where/actions/workflows/test.yml) [![Docs.rs Documentation](https://img.shields.io/docsrs/derive-where)](https://docs.rs/crate/derive-where) ## Description Attribute proc-macro to simplify deriving standard and other traits with custom generic type bounds. ## Usage The [`derive_where`] attribute can be used just like std's `#[derive(...)]` statements: ```rust #[derive_where(Clone, Debug)] struct Example(PhantomData); ``` This will generate trait implementations for `Example` for any `T`, as opposed to std's derives, which would only implement these traits with `T: Trait` bound to the corresponding trait. Multiple [`derive_where`] attributes can be added to an item, but only the first one must use any path qualifications. ```rust #[derive_where::derive_where(Clone, Debug)] #[derive_where(Eq, PartialEq)] struct Example1(PhantomData); ``` If using a different package name, you must specify this: ```rust #[derive_where(crate = derive_where_)] #[derive_where(Clone, Debug)] struct Example(PhantomData); ``` In addition, the following convenience options are available: ### Generic type bounds Separated from the list of traits with a semi-colon, types to bind to can be specified. This example will restrict the implementation for `Example` to `T: Clone`: ```rust #[derive_where(Clone, Debug; T)] struct Example(T, PhantomData); ``` It is also possible to specify the bounds to be applied. This will bind implementation for `Example` to `T: Super`: ```rust trait Super: Clone + Debug {} #[derive_where(Clone, Debug; T: Super)] struct Example(PhantomData); ``` But more complex trait bounds are possible as well. The example below will restrict the [`Clone`] implementation for `Example` to `T::Type: Clone`: ```rust trait Trait { type Type; } struct Impl; impl Trait for Impl { type Type = i32; } #[derive_where(Clone, Debug; T::Type)] struct Example(T::Type); ``` Any combination of options listed here can be used to satisfy a specific constrain. It is also possible to use multiple separate constrain specifications when required: ```rust #[derive_where(Clone, Debug; T)] #[derive_where(Eq, PartialEq; U)] struct Example(PhantomData, PhantomData); ``` ### Enum default Since Rust 1.62 deriving [`Default`] on an enum is possible with the `#[default]` attribute. Derive-where allows this with a `#[derive_where(default)]` attribute: ```rust #[derive_where(Clone, Default)] enum Example { #[derive_where(default)] A(PhantomData), } ``` ### Skipping fields With a `skip` or `skip_inner` attribute fields can be skipped for traits that allow it, which are: [`Debug`], [`Hash`], [`Ord`], [`PartialOrd`], [`PartialEq`], [`Zeroize`] and [`ZeroizeOnDrop`]. ```rust #[derive_where(Debug, PartialEq; T)] struct Example(#[derive_where(skip)] T); assert_eq!(format!("{:?}", Example(42)), "Example"); assert_eq!(Example(42), Example(0)); ``` It is also possible to skip all fields in an item or variant if desired: ```rust #[derive_where(Debug, PartialEq)] #[derive_where(skip_inner)] struct StructExample(T); assert_eq!(format!("{:?}", StructExample(42)), "StructExample"); assert_eq!(StructExample(42), StructExample(0)); #[derive_where(Debug, PartialEq)] enum EnumExample { #[derive_where(skip_inner)] A(T), } assert_eq!(format!("{:?}", EnumExample::A(42)), "A"); assert_eq!(EnumExample::A(42), EnumExample::A(0)); ``` Selective skipping of fields for certain traits is also an option, both in `skip` and `skip_inner`. To prevent breaking invariants defined for these traits, some of them can only be skipped in groups. The following groups are available: - [`Debug`] - `EqHashOrd`: Skips [`Eq`], [`Hash`], [`Ord`], [`PartialOrd`] and [`PartialEq`]. - [`Hash`] - `Zeroize`: Skips [`Zeroize`] and [`ZeroizeOnDrop`]. ```rust #[derive_where(Debug, PartialEq)] #[derive_where(skip_inner(Debug))] struct Example(i32, PhantomData); assert_eq!(format!("{:?}", Example(42, PhantomData::<()>)), "Example"); assert_ne!( Example(42, PhantomData::<()>), Example(0, PhantomData::<()>) ); ``` ### Incomparable variants/items Similar to the `skip` attribute, `incomparable` can be used to skip variants or items in [`PartialEq`] and [`PartialOrd`] trait implementations, meaning they will always yield `false` for `eq` and `None` for `partial_cmp`. This results in all comparisons but `!=`, i.e. `==`, `<`, `<=`, `>=` and `>`, with the marked variant or struct evaluating to `false`. ```rust # use derive_where::derive_where; #[derive(Debug)] #[derive_where(PartialEq, PartialOrd)] enum EnumExample { #[derive_where(incomparable)] Incomparable, Comparable, } assert_eq!(EnumExample::Comparable, EnumExample::Comparable); assert_ne!(EnumExample::Incomparable, EnumExample::Incomparable); assert!(!(EnumExample::Comparable >= EnumExample::Incomparable)); assert!(!(EnumExample::Comparable <= EnumExample::Incomparable)); assert!(!(EnumExample::Incomparable >= EnumExample::Incomparable)); assert!(!(EnumExample::Incomparable <= EnumExample::Incomparable)); #[derive(Debug)] #[derive_where(PartialEq, PartialOrd)] #[derive_where(incomparable)] struct StructExample; assert_ne!(StructExample, StructExample); assert!(!(StructExample >= StructExample)); assert!(!(StructExample <= StructExample)); ``` Note that it is not possible to use `incomparable` with [`Eq`] or [`Ord`] as that would break their invariants. ### `Zeroize` options `Zeroize` has two options: - `crate`: an item-level option which specifies a path to the [`zeroize`] crate in case of a re-export or rename. - `fqs`: a field-level option which will use fully-qualified-syntax instead of calling the [`zeroize`][method@zeroize] method on `self` directly. This is to avoid ambiguity between another method also called `zeroize`. ```rust #[derive_where(Zeroize(crate = zeroize_))] struct Example(#[derive_where(Zeroize(fqs))] i32); impl Example { // If we didn't specify the `fqs` option, this would lead to a compile // error because of method ambiguity. fn zeroize(&mut self) { self.0 = 1; } } let mut test = Example(42); // Will call the struct method. test.zeroize(); assert_eq!(test.0, 1); // WIll call the `Zeroize::zeroize` method. Zeroize::zeroize(&mut test); assert_eq!(test.0, 0); ``` ### `ZeroizeOnDrop` options If the `zeroize-on-drop` feature is enabled, it implements [`ZeroizeOnDrop`] and can be implemented without [`Zeroize`], otherwise it only implements [`Drop`] and requires [`Zeroize`] to be implemented. [`ZeroizeOnDrop`] has one option: - `crate`: an item-level option which specifies a path to the [`zeroize`] crate in case of a re-export or rename. ```rust #[derive_where(ZeroizeOnDrop(crate = zeroize_))] struct Example(i32); assert!(core::mem::needs_drop::()); ``` ### Supported traits The following traits can be derived with derive-where: - [`Clone`] - [`Copy`] - [`Debug`] - [`Default`] - [`Eq`] - [`Hash`] - [`Ord`] - [`PartialEq`] - [`PartialOrd`] - [`Zeroize`]: Only available with the `zeroize` crate feature. - [`ZeroizeOnDrop`]: Only available with the `zeroize` crate feature. If the `zeroize-on-drop` feature is enabled, it implements [`ZeroizeOnDrop`], otherwise it only implements [`Drop`]. ### Supported items Structs, tuple structs, unions and enums are supported. Derive-where tries it's best to discourage usage that could be covered by std's `derive`. For example unit structs and enums only containing unit variants aren't supported. Unions only support [`Clone`] and [`Copy`]. [`PartialOrd`] and [`Ord`] need to determine the discriminant type to function correctly. To protect against a potential future change to the default discriminant type, some compile-time validation is inserted to ascertain that the type remains `isize`. ### `no_std` support `no_std` support is provided by default. ## Crate features - `nightly`: Implements [`Ord`] and [`PartialOrd`] with the help of [`core::intrinsics::discriminant_value`], which is what Rust does by default too. This requires a nightly version of the Rust compiler. - `safe`: `safe`: Uses only safe ways to access the discriminant of the enum for [`Ord`] and [`PartialOrd`]. It also replaces all cases of [`core::hint::unreachable_unchecked`] in [`Ord`], [`PartialEq`] and [`PartialOrd`], which is what std uses, with [`unreachable`]. - `zeroize`: Allows deriving [`Zeroize`] and [`zeroize`][method@zeroize] on [`Drop`]. - `zeroize-on-drop`: Allows deriving [`Zeroize`] and [`ZeroizeOnDrop`] and requires [`zeroize`] v1.5. ## MSRV The current MSRV is 1.57 and is being checked by the CI. A change will be accompanied by a minor version bump. If MSRV is important to you, use `derive-where = "~1.x"` to pin a specific minor version to your crate. ## Alternatives - [derivative](https://crates.io/crates/derivative) [![Crates.io](https://img.shields.io/crates/v/derivative.svg)](https://crates.io/crates/derivative) is a great alternative with many options. Notably it doesn't support `no_std` and requires an extra `#[derive(Derivative)]` to use. - [derive_bounded](https://crates.io/crates/derive_bounded) [![Crates.io](https://img.shields.io/crates/v/derive_bounded.svg)](https://crates.io/crates/derive_bounded) is a new alternative still in development. ## Changelog See the [CHANGELOG] file for details. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE] or ) - MIT license ([LICENSE-MIT] or ) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [CHANGELOG]: https://github.com/ModProg/derive-where/blob/main/CHANGELOG.md [LICENSE-MIT]: https://github.com/ModProg/derive-where/blob/main/LICENSE-MIT [LICENSE-APACHE]: https://github.com/ModProg/derive-where/blob/main/LICENSE-APACHE [`Debug`]: https://doc.rust-lang.org/core/fmt/trait.Debug.html [`Default`]: https://doc.rust-lang.org/core/default/trait.Default.html [`Hash`]: https://doc.rust-lang.org/core/hash/trait.Hash.html [`zeroize`]: https://docs.rs/zeroize [`Zeroize`]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html [`ZeroizeOnDrop`]: https://docs.rs/zeroize/latest/zeroize/trait.ZeroizeOnDrop.html [method@zeroize]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html#tymethod.zeroize [`Clone`]: https://doc.rust-lang.org/core/clone/trait.Clone.html [`Copy`]: https://doc.rust-lang.org/core/marker/trait.Copy.html [`core::hint::unreachable_unchecked`]: https://doc.rust-lang.org/core/hint/fn.unreachable_unchecked.html [`core::intrinsics::discriminant_value`]: https://doc.rust-lang.org/core/intrinsics/fn.discriminant_value.html [`derive_where`]: https://docs.rs/derive-where/latest/derive_where/attr.derive_where.html [`Discriminant`]: https://doc.rust-lang.org/core/mem/struct.Discriminant.html [`Drop`]: https://doc.rust-lang.org/core/ops/trait.Drop.html [`Eq`]: https://doc.rust-lang.org/core/cmp/trait.Eq.html [`i32`]: https://doc.rust-lang.org/core/primitive.i32.html [`isize`]: https://doc.rust-lang.org/core/primitive.isize.html [`Ord`]: https://doc.rust-lang.org/core/cmp/trait.Ord.html [`PartialEq`]: https://doc.rust-lang.org/core/cmp/trait.PartialEq.html [`PartialOrd`]: https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html [`transmute`]: https://doc.rust-lang.org/core/mem/fn.transmute.html [`unreachable`]: https://doc.rust-lang.org/core/macro.unreachable.html