'trait-gen' 软件包
该软件包提供了属性宏,用于为参数中指定的所有类型生成附带的实现。它最初是为 trait 实现而设计的,因此得名,但也可用于任何泛型实现。
以下是一个简单的示例:
use trait_gen::trait_gen;
trait MyLog { fn my_log2(self) -> u32; }
#[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 类型的引用和智能指针:
#[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)
}
}
元组、排列与条件生成
当多个参数共享类型列表时(换句话说,当我们需要_带重复的排列_或_元组_时),可以使用更简洁的格式:
#[trait_gen(T, U -> u8, u16, u32)]
在以下示例中,我们还展示了条件属性 trait_gen_if,
它为实现提供了更大的灵活性。该条件具有一般格式
<argument> in <types>,或其否定形式,!<argument> in <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)))
}
}
这将为我们提供所有从/到 u8、u16 和 u32 的转换,除了相同类型之间的转换,因为它们已由标准库中的通用实现所覆盖。trait_gen_if 对于根据实现类型选择常量或移除方法也非常有用。
注意:感谢 Daniel Vigovszky 给我条件生成的想法!他最初在一个名为 conditional_trait_gen 的分支中实现了它,尽管方式大不相同。我在旧帖子中曾考虑过一些需要此功能的使用场景,但直到 1.1.0 版本才着手实现。
上述效果可以通过以下更短的格式实现,该格式生成 T 和 U 的所有类型组合,使得 T != U(2-排列):
#[trait_gen(T != U -> u8, u16, u32)]
impl From<Wrapper<U>> for Wrapper<T> { /* ... */ }
其他特性
请阅读 crate 文档 以获取更多详细信息。
动机
还有其他生成多个实现的方式:
- 手动复制,这很繁琐、容易出错且难以维护
- 使用声明式宏
- 使用 blanket implementation
使用 声明式宏 会得到类似以下内容:
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 }
它能完成任务,但比原生代码更难阅读,而且 IDE 通常无法在宏代码中提供上下文帮助或应用重构。当我们查找由声明式宏生成的方法定义时,得到以下代码行也相当令人烦恼且毫无帮助:
impl_my_log! { u8 u16 u32 u64 u128 }
使用通用实现非常强大,但存在其他缺点:
- 它禁止为同一 crate 中尚未被通用实现覆盖的类型之外的任何类型提供实现,因此仅当实现可以为所有约束类型(包括当前和未来的类型)编写时才有效。
- 并不总能找到与我们需要编写的代码相对应的 trait。例如,
numcrate 为基本类型提供了大量帮助,但并非所有内容都被覆盖。 - 为整个类型范围进行实现并不总是可取的。
- 即使操作和常量由 trait 覆盖,也很快需要一长串 trait 约束。
我认为,由于语言本身缺乏为类型列表实现通用代码的默认方式,过程宏是次优选择。
兼容性
trait-gen crate 在 Windows 64 位和 Linux 64/32 位平台上针对 Rust 版本 1.61.0 及稳定版进行了测试。
发布
RELEASES.md 记录了所有发布(大多数也在 GitHub release page 上)。
许可证
本代码根据 MIT License 或 Apache License 2.0 进行许可,供您选择。