Rust 中,trait,关联类型,泛型,这几个概念本身并不复杂。但是这些东西合在一起使用的时候,经常让初学者感觉天花乱坠,摸不着头脑。本文就用一些简单的例子,来梳理一下这些概念,以及它们之间的配合使用方式。
关联类型
关联类型是 trait 定义中的类型占位符。定义的时候,并不定义它的具体的类型是什么。在 impl 这个 trait 的时候,才为这个关联类型赋予确定的类型。也就是说,在实现的时候,才知道它的具体类型是什么。
举个例子,我们自定义一个 trait 叫:Converter
。
pub trait Converter {
type Output;
fn convert(&self) -> Self::Output;
}
例子:
代码语言:javascript复制pub trait Converter {
type Output;
fn convert(&self) -> Self::Output;
}
struct MyInt;
impl Converter for MyInt {
type Output = i32;
fn convert(&self) -> Self::Output {
42
}
}
fn main() {
let my_int = MyInt;
let output = my_int.convert();
println!("output is: {}", output);
}
输出:
output is: 42
trait 中的泛型参数
其实使用泛型也可以做到类似的效果。如果不使用关联类型,trait 可以这样定义(示意):
代码语言:javascript复制pub trait Converter<T> {
fn convert(&self) -> T;
}
例子:
代码语言:javascript复制pub trait Converter<T> {
fn convert(&self) -> T;
}
struct MyInt;
impl Converter<i32> for MyInt {
fn convert(&self) -> i32 {
42
}
}
impl Converter<f32> for MyInt {
fn convert(&self) -> f32 {
52.0
}
}
fn main() {
let my_int = MyInt;
// Error: could not use turbofish syntax here
// let output = my_int.convert::<i32>();
let output: i32 = my_int.convert();
println!("output is: {}", output);
// Error: could not use turbofish syntax here
// let output = my_int.convert::<f32>();
let output: f32 = my_int.convert();
println!("output is: {}", output);
}
输出:
output is: 42
output is: 52
可以看到,在 trait 中,带上泛型参数,也可以实现关联类型同样的工作。但是,它们之间有区别。
trait 中的泛型与关联类型,有如下区别:
- 如果 trait 中包含泛型参数,那么,可以对同一个目标类型,多次 impl 此 trait,每次提供不同的泛型参数。而关联类型方式只允许对目标类型实现一次。
- 如果 trait 中包含泛型参数,那么在具体方法调用的时候,必须加以类型标注以明确使用的是哪一个具体的实现。而关联类型方式具体调用时不需要标注类型(因为不存在模棱两可的情况)。
trait 中的泛型参数 默认类型
泛型参数是可以指定默认类型的,在 trait 的定义中也不例外。
例子:
代码语言:javascript复制pub trait Converter<T=i32> {
fn convert(&self) -> T;
}
struct MyInt;
impl Converter for MyInt {
fn convert(&self) -> i32 {
42
}
}
impl Converter<f32> for MyInt {
fn convert(&self) -> f32 {
52.0
}
}
fn main() {
let my_int = MyInt;
let output: i32 = my_int.convert();
println!("output is: {}", output);
let output: f32 = my_int.convert();
println!("output is: {}", output);
}
输出:
output is: 42
output is: 52
可以看到,对于默认的类型,实现的时候,不需要带类型参数。
关联类型与泛型参数一起使用
前面我们做好了一些准备,下面我们看看关联类型与泛型参数如何一起使用。
代码语言:javascript复制pub trait Converter<T> {
type Output;
fn convert(&self) -> (Self::Output, T);
}
struct MyInt;
impl Converter<i32> for MyInt {
type Output = i32;
fn convert(&self) -> (Self::Output, i32) {
(42, 42)
}
}
impl Converter<f32> for MyInt {
type Output = i32;
fn convert(&self) -> (Self::Output, f32) {
(52, 52.0)
}
}
fn main() {
let my_int = MyInt;
let output: (i32, i32) = my_int.convert();
println!("output is: {:?}", output);
let output: (i32, f32) = my_int.convert();
println!("output is: {:?}", output);
}
输出:
output is: (42, 42)
output is: (52, 52.0)
可以看到,其实它们之间没有必然的关系,本身维度是分开的。
关联类型、泛型参数、默认参数一起使用
在前面的例子基础上,添加了默认参数。
代码语言:javascript复制pub trait Converter<T=i32> {
type Output;
fn convert(&self) -> (Self::Output, T);
}
struct MyInt;
impl Converter for MyInt {
type Output = i32;
fn convert(&self) -> (Self::Output, i32) {
(42, 42)
}
}
impl Converter<f32> for MyInt {
type Output = i32;
fn convert(&self) -> (Self::Output, f32) {
(52, 52.0)
}
}
fn main() {
let my_int = MyInt;
let output: (i32, i32) = my_int.convert();
println!("output is: {:?}", output);
let output: (i32, f32) = my_int.convert();
println!("output is: {:?}", output);
}
输出:
output is: (42, 42)
output is: (52, 52.0)
仔细看,其实不复杂。
花式玩法:关联类型、泛型参数、默认参数、Self 一起使用
下面这个例子可以好好理解一下,虽然玩得有点花。
代码语言:javascript复制pub trait Converter<T=Self> {
type Output;
fn convert(&self) -> (Self::Output, T);
}
#[derive(Debug, Copy, Clone)]
struct MyInt(i32);
impl Converter for MyInt {
type Output = Self;
fn convert(&self) -> (Self::Output, Self) {
(*self, *self)
}
}
impl Converter<f32> for MyInt {
type Output = Self;
fn convert(&self) -> (Self::Output, f32) {
(*self, 52.0)
}
}
fn main() {
let my_int = MyInt(42);
let output: (MyInt, MyInt) = my_int.convert();
println!("output is: {:?}", output);
let output: (MyInt, f32) = my_int.convert();
println!("output is: {:?}", output);
}
输出:
output is: (MyInt(42), MyInt(42))
output is: (MyInt(42), 52.0)
好吧,就到这里为止吧,希望有点用处,以后看复杂代码的时候,不会眼花了 :D
本文所有代码在:https://github.com/daogangtang/learn-rust/tree/master/02trait_associated_generic
已测试,可以下载验证。