泛型#

Rust 泛型会在编译时根据参数将泛型单态化(Monomorphization ),因此,Rust 泛型在运行时是没有任何损耗的。

泛型在函数定义#

fn largest<T: std::cmp::PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 43, 15];
    println!("the largest number is {}", largest(&number_list));

    let char_list = vec!['y', 'm', 'c', 'd'];
    println!("the largest char is {}", largest(&char_list));
}

泛型在结构体定义#

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let p = Point{x: 5, y: 3};
    println!("x is {}, y is {}", p.x, p.y);

    let p = Point{x: 5.0, y: 3.0};
    println!("x is {}, y is {}", p.x, p.y)
}

需要注意,这里表示结构体内x和y是同一种类型,如果需要x和y类型不同,需要定义两个泛型参数。

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let p = Point{x: 5, y: 3.0};
    println!("x is {}, y is {}", p.x, p.y);
}

泛型在枚举定义#

基本上和在结构体中类似:

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

泛型在方法定义#

impl<T: std::ops::Mul<Output = T> + Copy> Point<T> {
    fn area(& self) -> T {
        let result = self.x * self.y;
        result
    }
}

在impl后面指明<T>表示Point后面跟的类型是泛型,泛型结构体也可以只给其中某些类型定义方法:

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

在方法里,也可以定义其它的泛型参数,表示方法接收的参数的类型与结构体参数类型不一样:

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

特型#

特型类似于Go里面的接口,但是与接口又存在着一些区别。

pub trait Summary {
    fn summarize(&self) -> String;
}

实现特型#

在为一个类型实现特型的时候,与Go不同的是,Rust需要指定实现了哪类特型:

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

需要注意的是,只有在这两个条件至少满足其一的时候才能实现特型:

  1. type在本地crate中;
  2. trait在本地crate中

也就是说,我们不能在我们自己的crate中给Vec<T>实现一个Display特型。

默认实现#

相比Go的接口,Rust特型支持在定义的时候设置默认实现:

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("Hello World")
    }
}

特型作为参数#

和Go接口一样,特型可以作为参数定义在函数/方法内,接收所有实现了特型的类型参数:

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

特型边界(trait bound)#

除了上述使用特型的方式外,Rust还有一种详细模式的语法糖,称作特性边界(trait bound)。

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

这种表示方式等同于上面的表示方式(称作impl trait),在简单场景下,impl trait更加方便,但是trait bound能在更加复杂的场景下使用,如存在多个使用相同特型的参数:

pub fn notify<T: Summary>(item: &T, item2: &T) {
}

多个特型边界#

pub fn notify<T: Summary + Display>(item: &T)

pub fn notify(item: &(impl Summary + Display))

where 语法#

当参数的特型边界太多时,会使函数命名过于冗余,难以阅读。因此,Rust提供了where语法来在函数定义后面指定特型边界:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

fn some_function<T, U>(t: &T, u: &U) -> i32
	where T: Display + Clone,
		  U: Clone + Debug
{}

特型作为返回值#

fn return_summarizable() -> impl Summary {}

但是,在同一个函数中,我们只能使用impl trait返回同一种类型:

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from(
                "Penguins win the Stanley Cup Championship!",
            ),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            retweet: false,
        }
    }
}

上面的代码编译就会报错:

ubuntu@VM-16-5-ubuntu:~/workspace/rust/just_rust$ cargo run 
   Compiling just_rust v0.1.0 (/home/ubuntu/workspace/rust/just_rust)
error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:57:9
   |
44 |   /     if switch {
45 |   |         NewsArticle {
   |  _|_________-
46 | | |             headline: String::from(
47 | | |                 "Penguins win the Stanley Cup Championship!",
48 | | |             ),
...  | |
54 | | |             ),
55 | | |         }
   | |_|_________- expected because of this
56 |   |     } else {
57 | / |         Tweet {
58 | | |             username: String::from("horse_ebooks"),
59 | | |             content: String::from(
60 | | |                 "of course, as you probably already know, people",
...  | |
63 | | |             retweet: false,
64 | | |         }
   | |_|_________^ expected struct `NewsArticle`, found struct `Tweet`
65 |   |     }
   |   |_____- `if` and `else` have incompatible types
   |
help: you could change the return type to be a boxed trait object
   |
43 | fn returns_summarizable(switch: bool) -> Box<dyn Summary> {
   |                                          ^^^^^^^        ^
help: if you change the return type to expect trait objects, box the returned expressions
   |
45 |         Box::new(NewsArticle {
46 |             headline: String::from(
47 |                 "Penguins win the Stanley Cup Championship!",
48 |             ),
49 |             location: String::from("Pittsburgh, PA, USA"),
50 |             author: String::from("Iceburgh"),
 ...

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `just_rust`

To learn more, run the command again with --verbose.

方法中的特型边界#

我们可以用特型边界给特定的类型实现方法。

use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

我们也可以给任何实现了另外一种特型的类型实现特型:

impl<T: Display> ToString for T {
    // --snip--
}

比如,上面的代码就给所有实现了Display的类型实现了ToString特型。