This library provides attributes to generate the trait implementations for several types, without the need for custom declarative macros.
In the example below, the Add
trait is implemented for the types Meter
, Foot
and Mile
. The
T
type identifier is used to mark where the substitution takes place; it can be an existing type
or alias but it's not mandatory.
```rust use traitgen::traitgen;
pub struct Meter(f64); pub struct Foot(f64); pub struct Mile(f64);
impl Add for T { type Output = T;
fn add(self, rhs: T) -> Self::Output {
Self(self.0 + rhs.0)
}
} ```
The trait_gen
attribute has the same effect as this custom declarative macro:
```rust macrorules! impladd_length { ($($t:ty)*) => ( $(impl Add for $t { type Output = $t;
fn add(self, rhs: $t) -> Self::Output {
Self(self.0 + rhs.0)
}
})*
)
}
impladdlength! { Meter Foot Mile } ```
The advantages of the first method are the clarity of the native code, the support of refactoring tools, editor syntactic and semantic awareness, and not having to convert the code into a declarative macro. Looking for the definition of an implementation method is also much easier with the full support of code-aware editors!
The disadvantage is the current lack of support for procedural macros with the IntelliJ plugin, although this is an ongoing work (see tracking issue). A few work-arounds are discussed later.
There are also a few limitations of the current version described in the Limitations section.
```rust
impl Trait for T { // ... } ```
This attribute successively substitutes the first identifier of the list (T
), which is used as a
type in the attached source code, with each of the following types (type1
, type2
, type3
)
to generate all the variations.
The code must of course be compatible with all the types, or the compiler will trigger the
relevant errors. For example #[trait_gen(T -> u64, f64)]
cannot be used with Self(0)
because
0
is not a valid floating-point literal.
The attribute supports a shorter "legacy" format which was used in the earlier versions:
```rust
impl Trait for type1 { // ... } ```
Here, type2
and type3
are literally substituted for type1
to generate their implementation,
then the original code is implemented for type1
. This is a shortcut for the equivalent attribute
with the other format:
```rust
impl Trait for type1 { // ... } ```
Remark: this strange ordering comes from an optimization in the legacy format. This makes no difference, except the order of the compiler messages if there are warnings or errors in the code - that's the only reason we mention it here.
The short format can be used when there is little risk of confusion, like in the example below.
Meter
is unlikely to be used in all the variations, so using an alias is unnecessary. The type
to replace in the code must be in first position after the attribute:
```rust use std::ops::Add; use traitgen::traitgen;
pub struct Meter(f64); pub struct Foot(f64); pub struct Mile(f64);
impl Add for Meter { type Output = Meter;
fn add(self, rhs: Meter) -> Self::Output {
Self(self.0 + rhs.0)
}
} ```
In some situations, one of the implemented types happens to be also required in all the implementations. Consider the following example:
```rust pub trait ToU64 { fn into_u64(self) -> u64; }
impl ToU64 for u64 { fn into_u64(self) -> u64 { // ERROR! Replaced by -> i64, u32, ... self as u64 } } ```
This will not work because the return type of into_u64()
will be replaced too! To prevent it,
an alias must be used (or the other attribute format). This works:
```rust type T = u64;
impl ToU64 for T { fn into_u64(self) -> u64 { self as u64 } } ```
rust-analyzer supports procedural macros for code awareness, so everything should be fine for
editors based on this Language Server Protocol implementation. Unfortunately this isn't the
case of all IDEs yet, which removes some benefits of using this macro. For instance, the
IntelliJ plugin won't be able to provide much support while typing the code for an unknown
T
type, nor will it be able to look for the definition of the implemented methods or even
suggest them when writing code.
Two work-arounds can help until the support for procedural macros is more widely available:
Defining an alias for the identifier used in the attribute:
```rust pub trait ToU64 { fn into_u64(self) -> u64; }
type T = u64;
impl ToU64 for T { fn into_u64(self) -> u64 { self as u64 } } ```
Defining T
doesn't change the produced code, but it allows the editor to understand it without
expanding the macro.
Implementing for an existing type, and using it as the first identifier:
```rust pub trait AddMod { fn add_mod(self, other: Self, m: Self) -> Self; }
// No need to use type T = u32
in such a simple case:
impl AddMod for u32 { fn add_mod(self, other: Self, m: Self) -> Self { (self + other) % m } } ```
This is somewhat more confusing to read, and doesn't work if u32
must remain in all the
variations, like the u64
it the previous example just above.
Rust doesn't allow alias constructors, like T(1.0)
in the code below. When it is needed,
Self
or a trait associated type is usually equivalent: here, Self(1.0)
. In the rare event
that no substitute is available, consider using the Default
trait or creating a specific one.
```rust type T = Meter;
impl Neutral for T { fn mul_neutral(&self) -> Self { T(1.0) // <== ERROR, use Self(1.0) instead } } ```
The other attribute format suffers from the same problem, because of a limitation in the current version:
```rust
impl Neutral for T { fn mul_neutral(&self) -> Self { T(1.0) // <== ERROR, use Self(1.0) instead } } ```
The macro doesn't handle scopes, so it doesn't support any type declaration with the same name as the type that must be substituted. This, for instance, fails to compile:
```rust use num::Num; use traitgen::traitgen;
trait AddMod { type Output; fn add_mod(self, rhs: Self, modulo: Self) -> Self::Output; }
impl AddMod for T { type Output = T;
fn addmod(self, rhs: Self, modulo: Self) -> Self::Output {
fn intmod
If the T
identifier above is only a part of the type path, then it is fine. For example,
super::T
, T<u64>
or T(u64)
will not be replaced when using #[trait_gen(T, ...)]
. But on the other
hand, those type paths cannot be substituted either (yet) - you cannot use
#[trait_gen(T<u64>, ...)]
or #[trait_gen(super::T, ...)]
. This can be worked around by using
type aliases or a use
clause.
The trait-gen
crate is tested for rustc 1.67.1 and greater, on Windows 64-bit and Linux 64/32-bit platforms. There shouldn't be any problem with older versions.
RELEASES.md keeps a log of all the releases.
Licensed under MIT license.