Rust 中的泛型

本贴最后更新于 1430 天前,其中的信息可能已经时移世改

数据类型 是对同一类数据的抽象,而 泛型 是对具有一组相同行为的数据类型的抽象。Rust 使用 trait 来描述这一组相同的行为,简单的说 trait 就是一组函数的集合。Rust 中的 trait 类似于其他语言中的常被称为 接口(interfaces)的功能。

泛型

在函数中使用泛型

假如有两个函数,它们的功能是查找 slice 中最大值并返回,函数体中的采用的算法是一样的,主要不同的是参数类型。如下所示:

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

遇到这种情况我们就可以将重复部分抽取出来,用 泛型 代替参数中的数据类型。

// T 是泛型类型,它代表所有的数据类型。
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}
// 这样“任何”类型的`slice`都可以使用该函数了,而不用单独为每个类型都定义一个函数
fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

但是,上面的示例是不能编译通过的,因为存在两个问题:

  • 函数体中的大于运算符(>)用于比较两个 T 类型的值,但并不是所有的数据类型都能使用这个运算符。
  • list 参数的类型有可能是没有实现 Copy trait 的,这意味着我们可能不能将 list[0] 的值移动到 largest 变量中。

如果要完善这个函数,需要为 T 类型指定相应的 trait,来进行限定。

结构体中使用泛型

可以使用 <> 语法来定义拥有一个或多个泛型参数类型字段的结构体。

示例一:指定一个泛型参数

struct Point<T> {
    x: T,
    y: T,
}
// x 和 y 的类型必须相同
fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

示例二:指定两个泛型参数

struct Point<T, U> {
    x: T,
    y: U,
}
// x 和 y 的类型可以不同
fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

枚举中使用泛型

与结构体类似,枚举中也可以使用泛型。

示例一:标准库中的 Option<T>

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

示例二:标准库中的 Result<T, E>

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

方法定义中使用泛型

示例:在 Point<T> 结构体上实现方法 x,它返回 T 类型的字段 x 的引用

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

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

除了使用泛型,也可以为特定的数据类型定义一个方法。

示例

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

trait

使用 trait 可以帮助我们确保类型拥有期望的行为,对数据类型进行限定。

定义 trait

使用 trait 关键字定义 trait

示例

pub trait Summary {
    // 可以有多个方法:一行一个方法签名且都以分号结尾。
    fn summarize(&self) -> String;
}

实现 trait

示例

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)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

默认实现

trait 中可以提供默认的实现,当某个特定的类型实现 trait 时,可以选择保留或者重载这个默认的实现。

示例

// Summary trait 的定义,带有一个 summarize 方法的默认实现
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}
// NewsArticle 类型使用这个默认实现
impl Summary for NewsArticle {}
// 重载这个默认实现
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

trait 作为参数

trait 代表的是一组函数,因此可以用它来指定那些实现了特定 trait 的数据类型。

示例一

// 任何实现了 Summary 的类型都可以使用这个函数
// 可以认为是在泛型的基础上,添加了 trait 限定。这种语法称之为 trait bound。
pub fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}
// 这是对上面语法形式的简写
pub fn notify(item: impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

示例二:通过 + 指定多个 trait,数据类型必须实现指定的多个 trait

// 使用 impl 关键字
pub fn notify(item: impl Summary + Display) {}
// 使用 trait bound 语法
pub fn notify<T: Summary + Display>(item: T) {}

示例三:通过 where 简化 trait bound

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
{}

示例四:返回实现了 trait 的数据类型

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

相关资料

The Rust Programming Language

Rust by Example

  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    58 引用 • 22 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...