理解 Rust 泛型特征 (Generic Trait)
在 Rust 中,特征 (trait) 也可以是泛型。引入泛型特征,一是希望特征不受具体类别的限制,二是希望特征具有更广泛的约束性。
不受具体类别的限制
在 Rust 中,“转型”(cast) 实际上就是一种特征。在没有泛型特征的世界里,类型转换应该怎么办?
我们可以先写一个叫做CastFromI32
的特征。所有实现CastFromI32
特征的类型都可以从i32
类转型。
struct MyType {}
pub trait CastFromI32 {
fn from(_: i32) -> Self;
}
impl CastFromI32 for MyType {
fn from(origin: i32) -> Self {
// -- cast code --
}
}
这远远不够。现在,我还希望能从i64
/u32
/u64
/f8
/f32
/... 等其他类型转型。这意味着我们需要分别写出CastFromI64
/CastFromU32
/... 等一系列特征,然后期待开发者一个个实现他们。
struct MyType {}
pub trait CastFromI32 {
fn from(_: i32) -> Self;
}
pub trait CastFromI64 {
fn from(_: i64) -> Self;
}
pub trait CastFromU32 {
fn from(_: u32) -> Self;
}
// pub trait CastFrom ...
// damn, so much!
这无疑是无趣的。但如果有了泛型特征,我们就能这样做:
struct MyType {}
pub trait CastFrom<T> {
fn from(_: T) -> Self;
}
泛型特征使得 “特征不受具体类别的限制”。这样,方便我们写出更加简洁的代码。开发者也可以利用宏减少工作量。
更广泛的约束性
特征类似于其他语言的接口,具有约束性。在面向对象程序的编写中,我们经常写下Flyable
,Eatable
这种接口,让鸭子和披萨区别于其他类。在 Rust 里特征也是如此。
“运算符重载” 也通过特征实现。首先来看看加法特征长什么样。
pub trait Add<Rhs = Self> {
/// The resulting type after applying the `+` operator.
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
其中,Rhs
是泛型,指定被加数的类型,默认和加数类型一致(Self
)。Output
是关联类型,指定加法结果的类型。关联类型是泛型的特殊形式。
通过实现Add
特征,编写复数类的加法。我会这样写:
struct Complex {
real: f64,
imag: f64,
}
impl Add for Complex {
type Output = Complex;
fn add(self, rhs: Self) -> Self::Output {
Complex {
real: self.real + rhs.real,
imag: self.imag + rhs.imag,
}
}
}
可作为一个优秀的库作者,你还希望Complex
底层的实部、虚部支持更多的数据种类,例如i64
/u32
/u64
/f8
/f32
/... 等。这意味着我们需要把Complex
写成泛型,大概是这样:
struct Complex<T> {
real: T,
imag: T,
}
impl<T> Add for Complex<T> {
type Output = Complex<T>;
fn add(self, rhs: Self) -> Self::Output {
Complex {
real: self.real + rhs.real, // compile error ❌️ here!
imag: self.imag + rhs.imag,
}
}
}
编译…… 错误!这是因为,我们使用的泛型T
可能是任何类型,例如鸭子或者披萨,它们是没有加法的。编译器非常聪明地检查到了这一点。所以,T
需要被约束。约束的条件是 “T 具有加法,被加数类型和结果类型也为 T”,翻译成 Rust 就是T: Add<(T, )Output=T>
。
Add<T, Output=T>
的第一个T
指的是Rhs
,被加数的类型,在Add
特征里已经说过默认值是 Self
,所以我们这里省略也可以。第二个T
是关联类型Output
,结果的类型,需要指明是T
。
所以正确的写法是:
struct Complex<T> {
real: T,
imag: T,
}
impl<T> Add for Complex<T>
where T: Add<Output=T> // or, `where T: Add<T, Output=T>`
{
type Output = Complex<T>;
fn add(self, rhs: Self) -> Self::Output {
Complex {
real: self.real + rhs.real,
imag: self.imag + rhs.imag,
}
}
}
回顾我们的改动:将原来不顾一切的类型T
加上了约束。这个约束条件 “T 具有加法,被加数类型和结果类型也为 T” 较为抽象。我们通过调整Add
泛型特征里的具体类型使得约束成立。现在,你大概能理解为什么我说 “泛型特征具有更广泛的约束性”。
结语
撰写这篇文章时,是我接触 Rust 的第二天。文章内容肯定有很多不足之处,请斧正。
如果你觉得有帮助,可以继续关注我的博客,下方链接处有 RSS 订阅地址。
非经允许,请勿转载。
© LICENSED UNDER CC BY-NC-SA 4.0