1 stable release

new 2.0.0 Dec 6, 2024

#18 in #deref

Download history 137/week @ 2024-12-01

137 downloads per month
Used in deriving_via

MIT license

73KB
1.5K SLoC

logo


Matrix Test crate-name at crates.io crate-name at docs.rs


Welcome to Deriving via, a library that makes it easy to deal with Newtypes in Rust. This library provides a practical way to automatically derive implementations for newtype wrappers, in the spirit of Haskell's GeneralisedNewtypeDeriving and Deriving via extensions.

deriving via aims to be your tool of choice for handling newtype patterns in Rust. The library makes use of a DerivingVia macro to generate Deref trait implementations, which allow your types to behave as Smart Wrappers by automatically dereferencing into their underlying types.

Our library also introduces features such as explicit Generalised Newtype Deriving using the #[deriving] attribute, and a way to specify base types for derive generation using the #[deriving(Trait(via: Type))] syntax.

According to The Rust Reference, the Deref trait is typically only implemented for smart pointers in Rust. However, this library deviates from that policy. This library uses the Deref trait as a hack to implement the newtype pattern. If you are comfortable with this approach, this library is for you.

Deriving Deref Trait with DerivingVia Macro

The DerivingVia macro generates the Deref trait implementation.

In general, by having the Deref<Target = U> implementation for a type T, you can treat values of type T like values of type U with the help of the Deref coercion. This mechanism is mainly used by structs wrapping around values, such as std::rc::Rc or std::boxed::Box.

Types that derive DerivingVia, therefore, will behave as Smart Wrappers of the underlying type.

Example

DerivingVia macro generates Deref trait implementation. Therefore, even if the method call is not directly syntactically valid, the receiver type can be repeatedly dereferenced.

use deriving_via::DerivingVia;

#[derive(DerivingVia)]
pub struct Foo(i32);

fn main() {
  let foo = Foo(42);

  let i: i32 = foo.to_owned(); // This works.
}

Foo doesn't implement Clone trait, therefore foo.to_owned() doesn't work directly. However, Foo implements Deref trait; therefore foo is dereferenced to i32 and to_owned() is called for i32.

pub struct Foo(i32);

// generated by `[derive(DerivingVia)]` ---+
impl Deref for Foo {                    // |
  type Target = i32;                    // |
                                        // |
  fn deref(&self) -> &Self::Target {    // |
    &self.0                             // |
  }                                     // |
} // <-------------------------------------+

fn main() {
  let foo = Foo(42);

  // This works because of Deref trait.
  // ToOwned trait is implemented for i32.
  // Foo is dereferenced to i32 and to_owned for i32 is called.
  let i: i32 = foo.to_owned();
}

Explicit Generalised Newtype Deriving

#[deriving] attribute is available for explicit Generalised Newtype Deriving.

Example (GND)

use deriving_via::DerivingVia;

#[derive(DerivingVia)]
pub struct A(i32);

#[derive(DerivingVia)]
#[deriving(Display)]
pub struct B(A);

fn main() {
  let b = B(A(42));

  println!("{b}"); // prints "42"
}

Deriving via

Using the Deriving via feature, it is possible to generate derives from the implementation of a specific base of a multi-layered wrapped type.

Example (Deriving via)

This example does not use Deriving via feature.

use std::fmt::Display;

use deriving_via::DerivingVia;

#[derive(DerivingVia)]
pub struct A(i32);

#[derive(DerivingVia)]
pub struct B(A);

fn main() {
  let b = B(A(42));

  // `b.to_string()` uses `A::Display` impl (most nearest impl).
  assert_eq!(b.to_string(), "A(42)");
}

This example uses Deriving via feature. B derives Display trait from i32 impl.

use std::fmt::Display;

use deriving_via::DerivingVia;

#[derive(DerivingVia)]
pub struct A(i32);

impl Display for A {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "A({})", self.0)
  }
}

#[derive(DerivingVia)]
#[deriving(Display(via: i32))] // a new line
pub struct B(A);

fn main() {
  let b = B(A(42));

  // `b.to_string()` uses `B::Display` impl directly.
  assert_eq!(b.to_string(), "42");
}

#[transitive] attribute

By the way, when you want to derive Add, you can dereference up to i32, but not from i32 back to Self. Therefore, you need to derive From from i32 to Self. You also need to specify the #[transitive] attribute to specify the order in which to return. Some traits require #[transitive] attribute (see Available Derives section).

Note: From<T> for T is implemented by generic implementations.

Example (transitive)

The following example derives Add and Display for C. To implement Display, it is sufficient to dereference C to i32. However, to implement Add, it is necessary to dereference from i32 back to C. To do so, you need to derive From for every newtype. In addition, you need to specify the order in which to return from i32 to C using the #[transitive] attribute.

use std::fmt::Display;

use deriving_via::DerivingVia;

#[derive(DerivingVia)]
#[deriving(From)]
pub struct A(i32);

#[derive(DerivingVia)]
#[deriving(From)]
pub struct B(A);

#[derive(DerivingVia)]
#[deriving(From, Add(via: i32), Display(via: i32))]
#[transitive(i32 -> A -> B -> C)]
pub struct C(B);

fn main() {
  let c: C = C(B(A(42))) + C(B(A(42)));
  println!("{c}");
}

Available Derives

struct Base(Underlying);

#[derive(DerivingVia)]
#[deriving(<Derive>)]
struct Target(Base);
  • fmt
    • Display
      • requires: Base: Display or (via = <Type>) and Type: Display
  • ops
    • Eq
      • requires: Base: Eq or (via = <Type>) and Type: Eq
    • Ord
      • requires: Base: Ord or (via = <Type>) and Type: Ord
    • Add-like (Add, Sub)
      • requires: Base: From<Underlying>
      • limitations: one hop or #[transitive]
    • Mul-like (Mul, Div)
      • requires: Base: From<Underlying>
      • limitations: one hop or #[transitive]
    • Arithmetic (Add, Sub, Mul, Div)
      • requires: Base: From<Underlying>
      • limitations: one hop or #[transitive]
    • Index
      • requires: Base: Index or (via = <Type>) and Type: Index
    • IndexMut
      • requires: Base: IndexMut or (via = <Type>) and Type: IndexMut
    • DerefMut
      • requires: Base: DerefMut or (via = <Type>) and Type: DerefMut
  • hash
    • Hash
      • requires: Base: Hash or (via = <Type>) and Type: Hash
  • serde
    • Serialize
      • requires: Base: Serialize or (via = <Type>) and Type: Serialize
    • Deserialize
      • requires: Base: Deserialize or (via = <Type>) and Type: Deserialize
  • convert
    • AsRef
    • AsMut
    • FromIterator
      • requires: (via: <ItemType>)
    • IntoIterator
      • requires: Base: IntoIterator or (via: <Type>), Type: IntoIterator
    • Into
      • requires: Base: Into<Underlying>
      • limitations: one hop or #[transitive]
    • From
      • limitations: one hop or #[transitive]
    • TryFrom
      • requires: Base: From<Underlying>
      • limitations: one hop or #[transitive]
    • FromStr
      • requires: Base: From<Underlying>
      • limitations: one hop or #[transitive]
  • impls
    • Iter
      • requires: Base: IntoIterator and Base dereferenceable to slice or (via: <Type>), Type: IntoIterator and Type dereferenceable to slice
    • IntoInner
      • requires: Base: Clone or (via: <Type>), Type: Clone

Caveat

DerivingVia using a transitive case of Type Coercion. According to rumours, transitive Type Coercion is not fully supported yet.

See: https://doc.rust-lang.org/reference/type-coercions.html#coercion-types

Dependencies

~1.1–1.6MB
~31K SLoC