【Rust每周一知】Rust 中 trait、关联类型与泛型配合的常见模式

2019-12-30 16:24:42 浏览数 (1)

Rust 中,trait,关联类型,泛型,这几个概念本身并不复杂。但是这些东西合在一起使用的时候,经常让初学者感觉天花乱坠,摸不着头脑。本文就用一些简单的例子,来梳理一下这些概念,以及它们之间的配合使用方式。

关联类型

关联类型是 trait 定义中的类型占位符。定义的时候,并不定义它的具体的类型是什么。在 impl 这个 trait 的时候,才为这个关联类型赋予确定的类型。也就是说,在实现的时候,才知道它的具体类型是什么。

举个例子,我们自定义一个 trait 叫:Converter

代码语言:javascript复制
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

已测试,可以下载验证。

0 人点赞