trait-gen

crate documentation build status crate

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);

[trait_gen(T -> Meter, Foot, Mile)]

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.

The trait_gen attribute

```rust

[trait_gen(T -> type1, type2, type3)]

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.

Alternative format

The attribute supports a shorter "legacy" format which was used in the earlier versions:

```rust

[trait_gen(type1, type2, type3)]

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

[trait_gen(type1 -> type2, type3, type1)]

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);

[trait_gen(Meter, Foot, Mile)]

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; }

[trait_gen(u64, i64, u32, i32, u16, i16, u8, i8)]

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;

[trait_gen(T, i64, u32, i32, u16, i16, u8, i8)]

impl ToU64 for T { fn into_u64(self) -> u64 { self as u64 } } ```

Code awareness issues

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:

Limitations

Compatibility

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

RELEASES.md keeps a log of all the releases.

License

Licensed under MIT license.