1.16 泛型trait:泛型(类型参数)trait、关联类型trait
这篇文章以概念性和建议性的文字偏多,需要你对泛型类型参数和关联类型有一定了解。
1.16.1. trait的泛型方式
trait的泛型方式有两种:
- 泛型类型参数。例:
trait Foo<T> - 关联类型。例:
trait Foo{type Bar;}
两者的区别在于:
- 使用关联类型这种形式的效果就是对于指定类型的trait只有一个实现
- 使用泛型参数类型则可以有多个实现
这里有一个简单的建议:可以的话尽量使用关联类型。
1.16.2. 泛型(类型参数)trait
泛型trait要求必须指定所有的泛型类型参数,并重复写这些参数的约束(bounds)。
这么写维护起来会难一些。比如说如果添加泛型类型参数到某个trait,该trait的所有实现者都必须更新代码。
这种形式还可能会导致针对给定的类型,一个trait有多重实现的问题。编译器会更难推断你想要的到底是trait的哪个实例。有时候不得不调用类似FromIterator::<u32>::from_iter这样可以消除歧义的函数。
这个特性有的时候也会是一个优点,例如:
impl PatialEq<BookFormat> for Book,其中BookFormat就可以是不同的类型- 可以同时实现
FromIterator<T>和FromIterator<&T> where T:Clone
1.16.3. 关联类型trait
我们以一段代码为例:
#![allow(unused)]
fn main() {
trait Contains {
type A;
type B;
//Updates syntax to refer to these new types generically
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}
}
使用关联类型:
- 编译器只需要知道实现trait的类型
- 约束(bound) 可以完全位于trait本身,不必重复使用
- 未来再添加关联类型也不影响用户使用
- 具体的类型会决定trait内关联类型的类型,无需使用消除歧义的函数,看个例子:
#![allow(unused)]
fn main() {
impl Contains for Container {
// Specify what types `A` and `B` are. If the `input` type
// is `Container(i32, i32)`, the `output` types are determined
// as `i32` and `i32`.
type A = i32;
type B = i32;
// `&Self::A` and `&Self::B` are also valid here.
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// Grab the first number.
fn first(&self) -> i32 { self.0 }
// Grab the last number.
fn last(&self) -> i32 { self.1 }
}
}
- 这个例子写了为
Container类型实现Containstrait Container类型的Containstrait中的关联类型通过type A = i32;和type B = i32;这两行代码决定
不可以对多个目标(Target)类型来实现Deref trait
看一下Deref trait的源代码:
#![allow(unused)]
fn main() {
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
}
type Target: ?Sized;中的Target就是我们说的目标(Target)类型
我们随便写一个Deref trait的实现来说明一下:
#![allow(unused)]
fn main() {
use std::ops::Deref;
struct Wrapper {
value: String,
}
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.value
}
}
}
上面代码中,Wrapper只能解引用为String。但如果你想让Wrapper同时解引用为String和str,Rust不允许你再实现Deref,因为Target只能有一个具体类型。
也就是说,这么写是非法的:
#![allow(unused)]
fn main() {
use std::ops::Deref;
struct Wrapper {
value: String,
}
// 第一次实现 Deref,Target = String
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.value
}
}
// 这是非法的,Rust 不允许对同一个类型 `Wrapper` 进行第二次 `Deref` 实现
impl Deref for Wrapper {
type Target = str; // 冲突,Rust 不能推断哪个 `Target` 生效
fn deref(&self) -> &Self::Target {
&self.value
}
}
}
不可以使用多个Item来实现Iterator trait
其原因与不可以对多个目标(Target)类型来实现Deref trait一样,主要涉及关联类型的唯一性和Rust 编译器的推断规则。