Expand description
This crate provides attribute macros that generate the attached implementation for all the types given in argument. It was first intended for trait implementations, hence the crate name, but it can also be used for any generic implementation.
§Usage
The attribute is placed before the pseudo-generic code to implement. The generic arguments
are given first, followed by a right arrow (->
) and a list of types that will replace the
argument in the generated implementations:
#[trait_gen(T -> Type1, Type2, Type3)]
impl Trait for T {
// ...
}
The attribute macro successively substitutes the generic argument T
in the code with
the given types (Type1
, Type2
, Type3
) to generate each implementation.
All the type paths beginning
with T
in the code have that part replaced. For example, T::default()
generates
Type1::default()
, Type2::default()
and so on, but super::T
is unchanged. Similarly, all
the types including T
in the code have that
part replaced; for example, &T
or Box<T>
.
The compiler will trigger an error if the resulting code is wrong. 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 of T
replaces any occurrence of ${T}
in doc comments, macros, and
string literals.
Notes:
- Using the letter “T” is not mandatory; any type path will do. For example,
g::Type
is fine too. But to make it easy to read and similar to a generic implementation, short upper-case identifiers are preferred. - If a
<..>
is required in the generic argument, the turbofish syntax must be used. For example, use#[trait_gen(T::<U> -> ...)
and not#[trait_gen(T<U> -> ...)
. type_gen
is a synonym attribute that can be used instead oftrait_gen
. This can be disabled with theno_type_gen
feature, in case it conflicts with another 3rd-party attribute.- There is no escape code to avoid the substitution in string literals; if you need
${T}
for another purpose and you don’t want it to be replaced, you can use this work-around:#[doc = concat!("my ${", "T} variable")]
. Or you can choose another generic argument, likeU
ormy::T
. - More complex formats with several arguments and conditions are shown in later examples.
Here is a simple 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
§Compositions
trait_gen
also replaces the content of inner attributes, so it’s possible to chain them
and extend the above example to references and smart pointers for all the T
types:
#[trait_gen(T -> u8, u16, u32, u64, u128)]
#[trait_gen(U -> &T, &mut T, Box<T>)]
impl MyLog for U {
/// Logarithm base 2 for `${U}`
fn my_log2(self) -> u32 {
MyLog::my_log2(*self)
}
}
§Tuples and Conditional Generation
A more concise format can be used when several arguments share the type lists (in other words, when we need permutations with repetitions, or tuples):
#[trait_gen(T, U -> u8, u16, u32)]
In the following example, we also show the conditional attribute trait_gen_if
, which
offers more flexibility in the implementations. The condition has the general format
<argument> in <types>
, or its negation, !<argument> in <types>
. The code is respectively
included or skipped when the argument is identical to one of the types.
use trait_gen::{trait_gen, trait_gen_if};
#[derive(Clone, PartialEq, Debug)]
struct Wrapper<T>(T);
#[trait_gen(T, U -> u8, u16, u32)]
// The types T and U must be different to avoid the compilation error
// "conflicting implementation in crate `core`: impl<T> From<T> for T"
#[trait_gen_if(!T in U)]
impl From<Wrapper<U>> for Wrapper<T> {
/// converts Wrapper<${U}> to Wrapper<${T}>
fn from(value: Wrapper<U>) -> Self {
Wrapper(T::try_from(value.0)
.expect(&format!("overflow when converting {} to ${T}", value.0)))
}
}
That will give us all the conversions from/to u8
, u16
, and u32
, except from the
same type since they’re already covered by a blanket implementation in the standard library.
trait_gen_if
is also very useful for selecting constants or removing methods depending on the
implementated type.
Notes:
- The number of generic arguments is not limited in this particular form, though it’s arguably hard to find relevant cases where more than two are required.
- We’ve seen earlier that
type_gen
was a synonym oftrait_gen
. For the sake of coherency, atype_gen_if
is provided as a synonym oftrait_gen_if
, too.
§Other Permutations
The implementation above could have been written more concisely with a 2-permutation, where
T != U
:
#[trait_gen(T != U -> u8, u16, u32)]
impl From<Wrapper<U>> for Wrapper<T> {
/// converts Wrapper<${U}> to Wrapper<${T}>
fn from(value: Wrapper<U>) -> Self {
Wrapper(T::try_from(value.0)
.expect(&format!("overflow when converting {} to ${T}", value.0)))
}
}
If we want to generate all the conversions from smaller integers to bigger integers,
similarly to what is done in the standard library
(with a cascade of declarative macros), we can use a 2-permutation with strict order,
meaning that index(T) < index(U)
—remember we can’t convert to the same type
because it conflicts with the blanket implementation in core
.
This will generate the code for (T, U)
= (u8, u16)
, (u8, u32)
, and (u16, u32)
(picture a triangle):
#[trait_gen(T < U -> u8, u16, u32)]
impl From<Wrapper<T>> for Wrapper<U> {
/// converts Wrapper<${T}> to Wrapper<${U}>
fn from(value: Wrapper<T>) -> Self {
Wrapper(U::from(value.0))
}
}
The non-strict order, where index(T) <= index(U)
, also exists for cases like
adding from another integer which has a smaller or equal length. This will generate
the code for (T, U)
= (u8, u8)
, (u8, u16)
, (u8, u32)
, (u16, u16)
, (u16, u32)
,
and (u32, u32)
.
#[trait_gen(T <= U -> u8, u16, u32)]
impl Add<Wrapper<T>> for Wrapper<U> {
type Output = Wrapper<U>;
fn add(self, rhs: Wrapper<T>) -> Self::Output {
Wrapper::<U>(self.0 + <U>::from(rhs.0))
}
}
Notes:
!=
,<
, and<=
are limited to two generic arguments.
That covers all the forms of these attributes. For more examples, look at the crate’s integration tests.
§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
g::T<U> -> ...
but not&T -> ...
.
Attribute Macros§
- trait_
gen - Generates the attached implementation code for all the types given in argument.
- trait_
gen_ if - Generates the attached code if the condition is met.
- type_
gen - Generates the attached implementation code for all the types given in argument.
- type_
gen_ if - Generates the attached code if the condition is met.