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 trait implementation 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.
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 struct
s 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.
When looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method (see Method-call expressions | The Rust Reference).
DerivingVia
macro generates Deref
trait implementation.
Therefore, newtypes that derive the DerivingVia
can make method calls to their underlying type.
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 for Foo
.
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();
}
#[deriving]
attribute is available for explicit Generalised Newtype Deriving.
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"
}
Using the Deriving via feature, it is possible to generate derives from the implementation of a specific base of a multi-layered wrapped type.
This example does not use Deriving via feature.
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)]
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` generated by deriving via i32
assert_eq!(b.to_string(), "42");
}
To implement Display
, it is sufficient to dereference Self
to the underlying type.
So what about the Add
trait?
Consider, for example, the i32
wrapper: dereferencing up to i32
is sufficient to perform addition.
However, 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 deriving require #[transitive]
attribute (see Available Derives section).
Note: From<T> for T
is implemented by generic implementations.
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}");
}
struct Base(Underlying);
#[derive(DerivingVia)]
#[deriving(<Derive>)]
struct Target(Base);
- fmt
Display
- requires:
Base: Display
or(via = <Type>) and Type: Display
- requires:
- ops
Eq
- requires:
Base: Eq
or(via = <Type>) and Type: Eq
- requires:
Ord
- requires:
Base: Ord
or(via = <Type>) and Type: Ord
- requires:
Add
-like (Add, Sub)- requires:
Base: From<Underlying>
- limitations: one hop or
#[transitive]
- requires:
Mul
-like (Mul, Div)- requires:
Base: From<Underlying>
- limitations: one hop or
#[transitive]
- requires:
Arithmetic
(Add, Sub, Mul, Div)- requires:
Base: From<Underlying>
- limitations: one hop or
#[transitive]
- requires:
Index
- requires:
Base: Index
or(via = <Type>) and Type: Index
- requires:
IndexMut
- requires:
Base: IndexMut
or(via = <Type>) and Type: IndexMut
- requires:
DerefMut
- requires:
Base: DerefMut
or(via = <Type>) and Type: DerefMut
- requires:
- hash
Hash
- requires:
Base: Hash
or(via = <Type>) and Type: Hash
- requires:
- serde
Serialize
- requires:
Base: Serialize
or(via = <Type>) and Type: Serialize
- requires:
Deserialize
- requires:
Base: Deserialize
or(via = <Type>) and Type: Deserialize
- requires:
- convert
AsRef
AsMut
FromIterator
- requires:
(via: <ItemType>)
- requires:
IntoIterator
- requires:
Base: IntoIterator
or(via: <Type>), Type: IntoIterator
- requires:
Into
- requires:
Base: Into<Underlying>
- limitations: one hop or
#[transitive]
- requires:
From
- limitations: one hop or
#[transitive]
- limitations: one hop or
TryFrom
- requires:
Base: From<Underlying>
- limitations: one hop or
#[transitive]
- requires:
FromStr
- requires:
Base: From<Underlying>
- limitations: one hop or
#[transitive]
- requires:
- impls
- Iter
- requires:
Base: IntoIterator and Base dereferenceable to slice
or(via: <Type>), Type: IntoIterator and Type dereferenceable to slice
- requires:
- IntoInner
- requires:
Base: Clone
or(via: <Type>), Type: Clone
- requires:
- Iter
DerivingVia
uses 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