Crate zoet[−][src]
Expand description
Adds #[zoet]
macro to reduce boilerplate when implementing common traits.
If you are sick of writing impl Deref for Bar
etc. and it didn’t compile because you confused it
with AsRef
, had a hard-to-debug problem because you implemented PartialOrd
and mistakenly
thought that deriving Ord
would do the sane thing, and/or you would rather just implement these
core traits as regular functions in your impl Bar
like lesser languages, this crate is for you!
It is superficially similar to the various derive macros such as derive_more
, except that rather
than generating traits based on the contents of a struct, it generates them based on individual
functions. An example works better than a textual description ever would:
use core::{
cmp::Ordering,
hash::{Hash, Hasher},
};
use zoet::zoet;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Length(usize);
#[zoet]
impl Length {
#[zoet(Default)] // generates `impl Default for Length`
pub fn new() -> Self {
Self(0)
}
#[zoet(From)] // generates `From<usize> for Length`
fn from_usize(value: usize) -> Self {
Self(value)
}
#[zoet(From)] // generates `From<Length> for usize`
fn to_usize(self) -> usize {
self.0
}
#[zoet(AsRef, Borrow, Deref)] // generates all of those.
fn as_usize(&self) -> &usize {
&self.0
}
#[zoet(Hash)] // see note below about traits with generic functions
fn hash(&self, state: &mut impl Hasher) {
self.0.hash(state)
}
#[zoet(Add, AddAssign)] // generates `impl Add for Length` and `impl AddAssign for Length`
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
#[zoet(Ord, PartialOrd)] // you get the idea by now
fn ord(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
let mut v = Length::default();
v += Length(1);
assert_eq!(v + Length(2), Length(3));
v += Length(4);
assert_eq!(v, Length(5));
assert_eq!(Length::from(v), Length(5));
Supported traits
Transformations for most traits in the standard library (core
, alloc
, and/or std
crates) are
provided. The current list is as follows:
core::borrow
:Borrow
andBorrowMut
.core::clone
:Clone
.core::cmp
:Ord
,PartialEq
, andPartialOrd
. (Eq
is recognised, but you’ll be told to#[derive(Eq)]
instead).core::convert
:AsMut
,AsRef
,From
,Into
,TryFrom
, andTryInto
.core::default
:Default
.core::fmt
:Binary
,Debug
,Display
,LowerExp
,LowerHex
,Octal
,Pointer
,UpperExp
UpperHex
, andWrite
(implementswrite_str
).core::future
:Future
.core::hash
:Hash
(implementshash
).core::iterator
:IntoIterator
andIterator
(implementsnext
).core::ops
:Deref
,DerefMut
,Drop
,Index
,IndexMut
, plus all arithmetic and bit ops and assignment variants such asAdd
andAddAssign
.core::str
:FromStr
.
The alloc
feature (which is enabled by default) also adds these:
alloc::borrow
:ToOwned
.alloc::string
:ToString
.
Most of the generated traits normally just include the trait boilerplate and forward the arguments to your method. There are a few useful extra special cases:
-
PartialOrd
can also be applied to anOrd
-shaped function, in which case it wraps the result withSome()
to make it fit.PartialEq
does the same with aPartialOrd
- orOrd
-shaped function and returns true if it returnsOrdering::Equal
. This allows you to do#zoet[(Ord, PartialEq, PartialOrd)]
to implement all of them in one go. -
Add
can also be applied to anAddAssign
-shaped function, in which case it generates a trivial implementation which mutates itsmut self
and returns it. This applies to all of the other operator traits withOpAssign
variants.
Unsupported traits
Since this macro turns single functions into traits, there needs to be a 1:1 mapping between a
function and a trait. This means that traits which require more than one function (e.g. Hasher
)
cannot be sanely supported. Likewise, marker traits like FusedIterator
are not supported even
though doing so would be trivial to implement, because that’s really a job for a derive macro.
Finally, traits which themselves have generic functions like FromIterator
are not supported
because they are beyond the abilities of zoet
current signature parser.
Additionally, traits which are nightly-only like Generator
are being avoided since there’s no
guarantee that zoet
will be able to keep track of any future updates.
Feel free to raise an issue or PR on the mooli/zoet-rs GitHub repository if you would like these to be added and have productive suggestions.
What it generates
A suitable impl is emitted which proxies to your function, such as this:
impl ::core::ops::Add<Self> for Length {
type Output = Length;
fn add(mut self, rhs: Length) -> Length {
<Length>::add_assign(&mut self, rhs);
self
}
}
impl ::core::ops::AddAssign<Length> for Length {
fn add_assign(&mut self, rhs: Length) {
<Length>::add_assign(self, rhs);
}
}
(This may look like infinite recursion at a first glance, but <Length>::add_assign
refers to the
method in the inherent impl. If you find this confusing, consider using a different method name.)
You can use cargo-expand
to check the actual expansion.
Gotchas
… due to Rust macro limitations
You must add #[zoet]
to your struct’s impl block so that the self type of its associated functions
can be determined. This is obviously not necessary (or possible) for free functions as they don’t
have a self type.
Because macros run before type checking, they only knows the names of the types, and not the
actual types. zoet
does the least possible parsing and checking of type names on the basis that
the compiler knows better than it does and can probably produce a better diagnostic, but for traits
such as TryInto
it needs to turn the return type of the function into the success and error types
required by the trait. This is where it gets a bit more strict and hairy, requiring the generic
types have specific names:
-
PartialOrd
requires its return type to beOption
. If not it willSome
-wrap it as if it wasOrd
(and hopefully give a compile-time error). -
Future
requires its parameters’ types to bePin
andPoll
respectively. -
Iterator
requires its return type to beOption
. -
TryFrom
,TryInto
,FromStr
require their return type to beResult
. If only one type parameter is given, the nameError
is used for the error type.
Only the last part of the path is compared, so eyre::Result
, crate::Result
,
::core::result::Result
etc. works just as well as a bare Result
.
This name-checking is only noted here in case you’re doing something very strange with type aliases, and should not affect sensible code.
… due to zoet
design limitations
Generic parameters on the function and/or its inherent impl are all just accumulated and added to
the trait impl’s generic parameters, which does the right thing for the vast majority of traits.
However, where a trait’s function is itself generic, zoet
isn’t (yet) smart enough to figure out
which of the generic parameter is for the function. As a perfectly good workaround, use an impl Trait
parameter instead. So while Hash
defines its single method as fn hash<H: Hasher>(&self, state: &mut H)
, your function needs to be have a signatue like fn hash(&self, state: &mut impl Hasher)
.
Elided lifetimes cannot be used on types which are copied into the generated traits, and the
function will need to be tweaked to have named lifetimes. This mainly affects traits such as
IntoIterator
, and in such cases you would change the signature from e.g. fn iter(&self) -> slice::Iter<T>
to fn iter<'a>(&'a self) -> slice::Iter<'a, T>
.
… due to you going overboard
While this macro makes it easy to stamp out loads of core traits, don’t go crazy but consider each trait you add and whether there is a more suitable macro to do the job, or indeed whether that trait should be added. Here are a few tips for avoiding using this crate:
-
The example above generates
Default
based onnew()
, but since that function returns 0 which is the default value anyway, it is better to#derive(Default)
and implementnew()
in terms of that. -
Similarly, its
Add
- andAddAssign
-adorned functions are trivial delegations to its field’sAdd
andAddAssign
traits. Thederive_more
crate handles this and will reduce the amount of boilerplate further, and in this case a simple#[derive(Add, AddAssign)]
on the struct will replace those functions. -
educe
lets you derive and customiseDebug
,Default
,Hash
,Clone
, andCopy
without writing actual boilerplate functions. -
Borrow
is not just a synonym forAsRef
, but gives specific guarantees, notably that “Eq
,Ord
andHash
must be equivalent for borrowed and owned values”. If yourAsRef
doesn’t offer those guarantees, don’t write#[zoet(AsRef, Borrow, Deref)]
.
Attribute Macros
The #[zoet]
macro.