Expand description
The trait_gen library
This library provides an attribute macro to generate the trait implementations for several types without needing custom declarative macros, code repetition, or blanket implementations. It makes the code easier to read and to maintain.
Here is a short example:
#[trait_gen(T -> u8, u16, u32, u64, u128)]
impl MyLog for T {
fn my_log2(self) -> u32 {
T::BITS - 1 - self.leading_zeros()
}
}
The trait_gen
attribute generates the following code by replacing T
with the types given as
arguments:
impl MyLog for u8 {
fn my_log2(self) -> u32 {
u8::BITS - 1 - self.leading_zeros()
}
}
impl MyLog for u16 {
fn my_log2(self) -> u32 {
u16::BITS - 1 - self.leading_zeros()
}
}
// and so on for the remaining types
Usage
The attribute is placed before the pseudo-generic implementation code. The generic argument
is given first, followed by a right arrow (->
) and a list of type arguments.
#[trait_gen(T -> Type1, Type2, Type3)]
impl Trait for T {
// ...
}
The attribute macro successively substitutes the generic argument T
in the code with
the following types (Type1
, Type2
, Type3
) to generate all the implementations.
All the type paths beginning with T
in the code have this part replaced. For example, T::default()
generates Type1::default()
,
Type2::default()
and so on, but super::T
is unchanged because it belongs to another scope.
The code must be compatible with all the types, or the compiler will trigger the relevant
errors. For example #[trait_gen(T -> u64, f64)]
cannot be applied to let x: T = 0;
because 0
is not a valid floating-point literal.
Finally, the actual type replaces any ${T}
occurrence in doc comments, macros, and string literals.
Notes:
- Using the letter “T” is not mandatory; any type path will do. For example,
gen::Type
is fine too. But to make it easy to read and similar to a generic implementation, short upper-case identifiers are preferred. - Two or more attributes can be chained to generate all the combinations.
trait_gen
can be used on type implementations too.
For more examples, look at the README.md or the crate integration tests.
Legacy Format
The attribute used a shorter format in earlier versions, which is still supported even though it may be more confusing to read:
#[trait_gen(Type1, Type2, Type3)]
impl Trait for Type1 {
// ...
}
is a shortcut for the equivalent attribute with the other format:
#[trait_gen(Type1 -> Type1, Type2, Type3)]
impl Trait for Type1 {
// ...
}
Alternative Format
An alternative format is also supported when the in_format
feature is enabled:
trait-gen = { version="0.3", features=["in_format"] }
Warning: This feature is temporary, and there is no guarantee that it will be maintained.
Here, in
is used instead of an arrow ->
, and the argument types must be between square brackets:
#[trait_gen(T in [u8, u16, u32, u64, u128])]
impl MyLog for T {
fn my_log2(self) -> u32 {
T::BITS - 1 - self.leading_zeros()
}
}
Using this format issues ‘deprecated’ warnings that you can turn off by adding the #![allow(deprecated)]
directive at the top of the file or by adding #[allow(deprecated)]
where the generated code is used.
Limitations
-
The procedural macro of the
trait_gen
attribute can’t handle scopes, so it doesn’t support any type declaration with the same literal as the generic argument. For instance, this code fails to compile because of the generic function:ⓘ#[trait_gen(T -> u64, i64, u32, i32)] impl AddMod for T { type Output = T; fn add_mod(self, rhs: Self, modulo: Self) -> Self::Output { fn int_mod<T: Num> (a: T, m: T) -> T { // <== ERROR, conflicting 'T' a % m } int_mod(self + rhs, modulo) } }
-
The generic argument must be a type path; it cannot be a more complex type like a reference or a slice. So you can use
gen::T<U> -> ...
but not&T -> ...
.
Attribute Macros
- Generates the attached trait implementation for all the types given in argument.