概述
trait-gen
是一个提供 trait 实现生成的属性宏的库。
它允许为多种类型生成 trait 实现,而无需自定义声明宏、代码重复或通用实现,从而使代码更易于阅读和维护。
使用示例
以下是一个简单的示例:
use trait_gen::trait_gen;
#[trait_gen(T -> u8, u16, u32, u64, u128)]
impl MyLog for T {
fn my_log2(self) -> u32 {
T::BITS - 1 - self.leading_zeros()
}
}
trait_gen
属性将 T
替换为给定的类型,生成如下代码:
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()
}
}
// 其他类型依此类推
使用方法
该属性放置在伪泛型实现代码之前。
泛型参数首先给出,后跟右箭头(->
)和类型参数列表。
#[trait_gen(T -> Type1, Type2, Type3)]
impl Trait for T {
// ...
}
属性宏会依次将代码中的泛型参数 T
替换为后续类型(Type1、Type2、Type3),生成所有实现。
所有以 T
开头的类型路径都会被替换。
例如,T::default()
会生成 Type1::default()
、Type2::default()
等,
但 super::T
保持不变,因为它属于另一个作用域。
代码必须与所有类型兼容,否则编译器将触发相关错误。例如,#[trait_gen(T -> u64, f64)]
不能应用于 let x: T = 0;
,因为 0
不是有效的浮点字面量。
实际类型还会替换文档注释、宏和字符串字面量中的任何 ${T}
出现。
注意事项
- 使用字母 "T" 不是强制性的,任何类型路径都可以。例如,
gen::Type
也是可以的。但为了提高可读性,建议使用简短的大写标识符。 - 可以链式使用两个或多个属性以生成所有组合。
trait_gen
也可以用于类型实现。
动机
生成多个实现的方法有几种:
- 手动复制
- 使用声明宏
- 使用通用实现
上面的实现示例可以通过声明宏实现:
macro_rules! impl_my_log {
($($t:ty)*) => (
$(impl MyLog for $t {
fn my_log2(self) -> u32 {
$t::BITS - 1 - self.leading_zeros()
}
})*
)
}
impl_my_log! { u8 u16 u32 u64 u128 }
但这种方法冗长且比原生代码难以阅读。
我 们必须每次编写自定义宏,包括其声明、模式和一些元素的转换(如参数 $t
)。
此外,IDE 通常无法提供上下文帮助或在宏代码中应用重构。
使用通用实现还有其他缺点:
- 除了同一 crate 中未被通用实现覆盖的类型外,禁止任何其他实现。
- 找到对应的 trait 并不总是可能。虽然
num
crate 对原始类型提供了很多帮助,但并不是所有情况都涵盖。 - 即使操作和常量被 trait 覆盖,也很快需要一长串 trait 约束。
示例
以下是支持的替换示例,库的集成测试中还有更多示例。
第一个示例更多是说明什么被替换,什么不被替换,而不是实际实现:
#[trait_gen(U -> u32, i32, u64, i64)]
impl AddMod for U {
fn add_mod(self, other: U, m: U) -> U {
const U: U = 0;
let zero = U::default();
let offset: super::U = super::U(0);
(self + other + U + zero + offset.0 as U) % m
}
}
扩展为(我们只展示第一个类型 ,u32
):
impl AddMod for u32 {
fn add_mod(self, other: u32, m: u32) -> u32 {
const U: u32 = 0;
let zero = u32::default();
let offset: super::U = super::U(0);
(self + other + U + zero + offset.0 as u32) % m
}
}
复杂示例
以下示例展示了如何使用类型参数:
struct Meter<U>(U);
struct Foot<U>(U);
trait GetLength<T> {
fn length(&self) -> T;
}
#[trait_gen(U -> f32, f64)]
impl GetLength<U> for Meter<U> {
fn length(&self) -> U {
self.0 as U
}
}
该属性可以与另一个属性组合,以创建泛型组合,
实现 Meter<f32>
、Meter<f64>
、Foot<f32>
、Foot<f64>
的 trait:
#[trait_gen(T -> Meter, Foot)]
#[trait_gen(U -> f32, f64)]
impl GetLength<U> for T<U> {
fn length(&self) -> U {
self.0 as U
}
}