[−][src]Crate zoet
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 methods 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/methods. An example works better than a textual description ever would:
use core::cmp::Ordering; 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(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));
Due to limitations in macro processing, you must add #[zoet] to your struct's impl block so
that the self type of its methods can be determined. This is obviously not necessary (or
possible) for free functions as they don't have a self type.
Transformations for most traits in the standard library are provided. Omitted are those which are just marker traits (there's no code to generate), those which require multiple functions, and some which don't quite seem worth it. The current list is as follows:
core::borrow:Borrow,BorrowMut.std::borrow/alloc::borrow:ToOwned.core::clone:Clone.core::cmp:Ord,PartialEq,PartialOrd.core::convert:AsMut,AsRef,From,Into,TryFrom,TryInto.core::default:Default.core::fmt:Debug,Display,Write(implements thewrite_strmethod).core::iterator:FromIterator,Iterator(implements thenextmethod).core::ops:Deref,DerefMut,Drop,Index,IndexMut, plus all arithmetic and bit ops and assignment variants such asAddandAddAssign. *core::str:FromStr.std::string/alloc::string:ToString.
These traits normally just include the trait boilerplate and forward the arguments to your method, however there are a couple of special cases which reduce boilerplate further:
-
PartialOrdcan also be applied to anOrd-shaped function, in which case it wraps the result withSome()to make it fit. This allows you to do#zoet[(Ord, PartialOrd)]to implement both with the same function and avoid order-related bugs. -
Addetc. can be applied to anAddAssign-shaped function, in which case it generates a trivial implementation which mutates itsmut selfand returns it.
Because macros run before type checking, they only knows the names of the types, and not the
actual types. zoet prefers to be liberal and pass through types rather than attempt to parse
them, but we need to unpick the result type used by some traits such as TryInto into the
success and error types, or rather, the names of the success and error types. As such, it
expects the result type to be called (or be a path ending in) Result or Fallible, and if
the second parameter is missing, the identifier Error is used. Idiomatic Rust code shouldn't
have a problem with this, but if you have unusual error-handling, you may trip over this.
However, 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. The
example above generates Default based on new(), but since that function returns 0 which is
the default value anyway, it'd be better to #derive(Default) and implement new() in terms
of that. Similarly, its Add and AddAssign trait implementations just delegating to its
field's Add and AddAssign traits, and the can be completely eliminated by using
derive_more and deriving Add and AddAssign on the struct. And if your struct doesn't
satisfy Borrow's invariants, you shouldn't unthinkingly do #[zoet(AsRef, Borrow, Deref)].
You are also reminded that cargo-expand exists, and can be used to inspect the expanded
text.
Modules
| traits | Re-exports of |
Attribute Macros
| zoet | The |