Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 68 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Motivation
----------

Instead of trying to be everything or deriving dozens of unused trait implementations,
this crate provides unique, simple, yet powerful tools for your `newtypes`.
this crate provides unique, simple, yet powerful tools for the `newtypes`.

The crate focuses on three main areas to make `newtype` usage more enjoyable:

Expand All @@ -26,16 +26,44 @@ The crate focuses on three main areas to make `newtype` usage more enjoyable:
Usage
-----

Adding the crate to your project:

```bash
cargo add newtype-tools
```

Examples
--------

Conversion between types:
The simplest way to use the crate is to declare a tuple struct as a `newtype` kind:

```rust
# #[cfg(feature = "derive")]
# {
#[newtype_tools::newtype(Amount)]
struct Apples(u64);

// Now the `Apples`behave pretty much as their inner type `u64`:
let apple1 = Apples(2);
// `Apples` can be converted from the inner type:
let apple2 = Apples::from(3);
// `Apples` can be added, subtracted and compared:
assert_eq!(apple1 + apple2, Apples(5));
// `Apples` can be multiplied by the inner factor:
assert_eq!(apple1 * 2_u64, Apples(4));
// `Apples` can be divided, returning a inner ratio:
assert_eq!(apple2 / apple1 , 1);
// `Apples` can be easily extended:
impl core::fmt::Display for Apples {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
# }
```

The crate supports two kinds of `newtypes`: `Amount` and `Id`. See below for more details.

Rather than using the predefined sets of derives, the implementation allows
for the derivation of only the necessary traits. Conversion between types:

```rust
# #[cfg(feature = "derive")]
Expand All @@ -50,6 +78,7 @@ struct Apples(u64);
struct Oranges(u32);

let apples = Apples(42);
// `Oranges` can now be created from `Apples`:
let oranges = Oranges::from(apples);

assert_eq!(oranges.0, 21);
Expand All @@ -73,6 +102,7 @@ struct Oranges(u32);
let apples = Apples(42);
let oranges = Oranges(21);

// `Apples` and `Oranges` can now be compared:
assert!(apples == oranges);
# }
```
Expand All @@ -88,6 +118,7 @@ use newtype_tools::{Newtype, Iter};
struct Apples(u64);

let range = Apples(0)..Apples(42);
// The range of `Apples` can now be iterated:
for apple in range.iter() {
println!("{apple:?}");
}
Expand All @@ -96,6 +127,39 @@ for apple in range.iter() {

This will become even more ergonomic once the [Step][step] trait is stabilized.

Newtype Kinds
-------------

The crate supports predefined sets of newtype properties. The concept is similar
to the `phantom_newtype` crate but avoids its limitations, as the newtype
generated here is a distinct Rust type. This allows new traits
to be implemented easily for the type and makes the set of derived traits
simple to extend.

The supported `newtype` kinds are:

| Trait | `#[newtype(Amount)]` | `#[newtype(Id)]` |
| ----------------- | :------------------: | :--------------: |
| `Clone` | ✔ | ✔ |
| `Copy` | ✔ | ✔ |
| `Debug` | ✔ | ✔ |
| `Default` | ✔ | ✔ |
| `Eq`¹ | ✔ | ✔ |
| `Hash`¹ | ✔ | ✔ |
| `Ord`¹ | ✔ | ✔ |
| `PartialEq` | ✔ | ✔ |
| `PartialOrd` | ✔ | ✔ |
| `From<Repr>` | ✔ | ✔ |
| `Add<Self>` | ✔ | ✘ |
| `AddAssign<Self>` | ✔ | ✘ |
| `Sub<Self>` | ✔ | ✘ |
| `SubAssign<Self>` | ✔ | ✘ |
| `Mul<Repr>` | ✔ | ✘ |
| `MulAssign<Repr>` | ✔ | ✘ |
| `Div<Self>` | ✔ | ✘ |

1. `Eq`, `Ord` and `Hash` are only implemented for integer inner types.

Alternatives
------------

Expand Down
25 changes: 17 additions & 8 deletions newtype-tools-derive/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ pub(crate) fn expand_newtype(
) -> proc_macro::TokenStream {
let newtype = &attr.newtype;
let inner_ty = &attr.inner_ty;
let (_impl_generics, newtype_generics, r#_where) = &attr.generics.split_for_impl();
let full_newtype = quote::quote! { #newtype #newtype_generics };

let standard_derives = quote::quote!(Clone, Copy, Debug, Default, PartialEq, PartialOrd);
let standard_derives = if is_int_type(inner_ty) {
quote::quote!(
#standard_derives, Eq, Ord, Hash
#standard_derives, Eq, Hash, Ord
)
} else {
standard_derives
Expand All @@ -26,18 +29,24 @@ pub(crate) fn expand_newtype(
#[automatically_derived]
#[derive(newtype_tools::Newtype, #standard_derives)]
#[newtype(
add(#newtype, output = #newtype, with = |l, r| #newtype(l.0 + r.0)),
add_assign(#newtype, with = |this, other| this.0 += other.0),
sub(#newtype, output = #newtype, with = |l, r| #newtype(l.0 - r.0)),
sub_assign(#newtype, with = |this, other| this.0 -= other.0),
mul(#inner_ty, output = #newtype, with = |l, inner| #newtype(l.0 * inner)),
mul_assign(#inner_ty, with = |this, inner| this.0 *= inner),
div(#newtype, output = #inner_ty, with = |l, r| l.0 / r.0)
add(#full_newtype, output = #full_newtype, with = |l, r| #newtype(l.0 + r.0)),
add_assign(#full_newtype, with = |this, other| this.0 += other.0),
sub(#full_newtype, output = #full_newtype, with = |l, r| #newtype(l.0 - r.0)),
sub_assign(#full_newtype, with = |this, other| this.0 -= other.0),
mul(#inner_ty, output = #full_newtype, with = |l, inner| #newtype(l.0 * *inner)),
mul_assign(#inner_ty, with = |this, inner| this.0 *= *inner),
div(#full_newtype, output = #inner_ty, with = |l, r| l.0 / r.0)
)]
// Guarantees the memory layout is identical to the inner type.
#[repr(transparent)]
}
.into(),
NewtypeKind::Id => quote::quote! {
#[automatically_derived]
#[derive(newtype_tools::Newtype, #standard_derives)]
#[repr(transparent)]
}
.into(),
};
derives.extend(item);
derives
Expand Down
30 changes: 11 additions & 19 deletions newtype-tools-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,18 @@ mod expand;
mod parse;

/// Parses and expands a `newtype` attribute kind into a token stream.
/// The idea is similar to the `phantom_newtype` crate, but has no its limitations,
/// as the `newtype` is a real new Rust type. New traits could be easily implemented
/// for such a `newtype`, and the set of derived traits could be easily extended:
///
/// The crate supports predefined sets of newtype properties. The concept is similar
/// to the `phantom_newtype` crate but avoids its limitations, as the newtype
/// generated here is a distinct Rust type. This allows new traits
/// to be implemented easily for the type and makes the set of derived traits
/// simple to extend.
///
/// ```ignore
/// #[newtype(Amount)]
/// #[derive(Default)]
/// struct Apples(u64);
/// ```
///
/// The supported `newtype` kinds are:
///
/// | Trait | `#[newtype(Amount)]` | `#[newtype(Id)]` |
/// |-------------------|:--------------------:|:----------------:|
/// | `PartialEq` | ✔ | ✔ |
/// | `PartialOrd` | ✔ | ✔ |
/// | `From<Repr>` | ✔ | ✔ |
/// | `Add<Self>` | ✔ | ✘ |
/// | `AddAssign<Self>` | ✔ | ✘ |
/// | `Sub<Self>` | ✔ | ✘ |
/// | `SubAssign<Self>` | ✔ | ✘ |
/// | `Mul<Repr>` | ✔ | ✘ |
/// | `MulAssign<Repr>` | ✔ | ✘ |
/// | `Div<Self>` | ✔ | ✘ |
#[proc_macro_attribute]
pub fn newtype(attr: TokenStream, item: TokenStream) -> TokenStream {
let kind = match parse::parse_newtype_kind(attr.into()) {
Expand Down Expand Up @@ -156,12 +144,14 @@ struct NewtypeDerives {
#[derive(Debug, PartialEq)]
enum NewtypeKind {
Amount,
Id,
}

impl core::fmt::Display for NewtypeKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Amount => f.write_str("Amount"),
Self::Id => f.write_str("Id"),
}
}
}
Expand All @@ -172,7 +162,8 @@ impl TryFrom<&syn::Ident> for NewtypeKind {
fn try_from(value: &syn::Ident) -> Result<Self, Self::Error> {
match value {
ident if ident == "Amount" => Ok(Self::Amount),
_ => Err(syn::Error::new_spanned(value, "expected 'Amount'")),
ident if ident == "Id" => Ok(Self::Id),
_ => Err(syn::Error::new_spanned(value, "expected 'Amount' or 'Id'")),
}
}
}
Expand Down Expand Up @@ -291,6 +282,7 @@ mod tests {
fn newtype_kind_display_roundtrip() {
use super::NewtypeKind;
assert_eq!(format!("{}", NewtypeKind::Amount), "Amount");
assert_eq!(format!("{}", NewtypeKind::Id), "Id");
}

#[test]
Expand Down
41 changes: 27 additions & 14 deletions newtype-tools/tests/newtype_amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,36 +162,49 @@ where
// Manual generic newtype trait definition.
#[derive(Clone, Copy, Debug, newtype_tools::Newtype, PartialEq, PartialOrd)]
#[newtype(
add(ManGeneric<T, R>, output = "ManGeneric<T, R>", with = "|a1, a2| ManGeneric(a1.0 + a2.0)"),
add_assign(ManGeneric<T, R>, with = "|this, other| this.0 += other.0"),
sub(ManGeneric<T, R>, output = "ManGeneric<T, R>", with = "|a1, a2| ManGeneric(a1.0 - a2.0)"),
sub_assign(ManGeneric<T, R>, with = "|this, other| this.0 -= other.0"),
mul(R, output = "ManGeneric<T, R>", with = "|a, r| ManGeneric(a.0 * *r)"),
mul_assign(R, with = "|this, r| this.0 *= *r"),
div(ManGeneric<T, R>, output = "R", with = "|a1, a2| a1.0 / a2.0")
add(ManGeneric<T>, output = "ManGeneric<T>", with = "|a1, a2| ManGeneric(a1.0 + a2.0)"),
add_assign(ManGeneric<T>, with = "|this, other| this.0 += other.0"),
sub(ManGeneric<T>, output = "ManGeneric<T>", with = "|a1, a2| ManGeneric(a1.0 - a2.0)"),
sub_assign(ManGeneric<T>, with = "|this, other| this.0 -= other.0"),
mul(T, output = "ManGeneric<T>", with = "|a, r| ManGeneric(a.0 * *r)"),
mul_assign(T, with = "|this, r| this.0 *= *r"),
div(ManGeneric<T>, output = "T", with = "|a1, a2| a1.0 / a2.0")
)]
#[repr(transparent)]
struct ManGeneric<T, R>(T)
struct ManGeneric<T>(T)
where
T: Clone
+ Copy
+ PartialEq
+ PartialOrd
+ core::fmt::Debug
+ From<R>
+ From<T>
+ core::ops::Add<T, Output = T>
+ core::ops::AddAssign<T>
+ core::ops::Sub<T, Output = T>
+ core::ops::SubAssign<T>
+ core::ops::Mul<R, Output = T>
+ core::ops::MulAssign<R>
+ core::ops::Div<T, Output = R>,
R: Copy + PartialOrd;
+ core::ops::Mul<T, Output = T>
+ core::ops::MulAssign<T>
+ core::ops::Div<T, Output = T>;

// Attribute generic newtype trait definition.
#[newtype_tools::newtype(Amount)]
/// Doc comment.
struct AttrGeneric(f64);
struct AttrGeneric<T>(T)
where
T: Clone
+ Copy
+ PartialEq
+ PartialOrd
+ core::fmt::Debug
+ From<T>
+ core::ops::Add<T, Output = T>
+ core::ops::AddAssign<T>
+ core::ops::Sub<T, Output = T>
+ core::ops::SubAssign<T>
+ core::ops::Mul<T, Output = T>
+ core::ops::MulAssign<T>
+ core::ops::Div<T, Output = T>;

#[rstest::rstest]
#[case::generic(2.0_f64, 3.0_f64, 2.0_f64)]
Expand Down
Loading