Rust中的泛型

[TOC]

Rust中的泛型

泛型程序设计是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。泛型编程的中心思想是从携带类型信息的具体的算法中抽象出来,得到一种可以与不同的数据类型表示相结合的算法,从而生成各种有用的软件。泛型编程是一种软件工程中的解耦方法,很多时候,我们的算法并不依赖某种特定的具体类型,通过这种方法,我们就可以将“类型”从算法和数据结构的具体示例中抽象出来。


泛型作为函数参数的类型

考虑以下问题:编写一个函数,这个函数接收两个数字,然后返回较大的那个数字。

fn largest(a: u32, b: u32) -> u32 {
    if a > b {
        a
    } else {
        b
    }
}

这个函数能工作,但它只能比较两个 u32 类型数字的大小。现在除了想比较两个 u32 外,还想比较两个 f32。有一种可以行的办法,我们可以定义多个 largest 函数,让它们分别叫做 largest_u32largest_f32… 这能正常工作,但不太美观。我们可以使用泛型语法对上述代码进行修改:

fn largest<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
    if a > b {
        a
    } else {
        b
    }
}

fn main() {
    println!("{}", largest::<u32>(1, 2));
    println!("{}", largest::<f32>(1.0, 2.1));
}

其中,std::cmp::PartialOrd 被称作泛型绑定,在之后的课程中我们会对此进行解释。


结构体中的泛型

我们还可以使用泛型语法定义结构体,结构体中的字段可以使用泛型类型参数。下面的代码展示了使用 Point<T> 结构来保存任何类型的 x 和 y 坐标值。

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

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

上述代码创建了一个 x 和 y 都是同一类型的 Point 结构体,但同时一个结构体中也可以包含多个不同的泛型参数:

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

fn main() {
    let integer = Point { x: 5, y: 10, z: 15.0 };
    let float = Point { x: 1.0, y: 4.0, z: 8 };
}

但是要注意,虽然一个结构体中可以包含任意多的泛型参数,但我仍然建议拆分结构体以使得一个结构体中只使用一个泛型参数。过多的泛型参数会使得阅读代码的人难以阅读。


结构体泛型的实现

我们可以在带泛型的结构体上实现方法,它的语法与普通结构体方法相差不大,只是要注意在它们的定义中加上泛型类型:

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

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

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

我们也可以在某种具体类型上实现某种方法,例如下面的方法将只在 Point<f32> 有效。

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

使用traits定义共同的行为

某一类数据可能含有一些共同的行为:例如它们能被显示在屏幕上,或者能相互之间比较大小。我们将这种共同的行为称作 Traits。我们使用标准库 std::fmt::Display 这个 traits 举例,这个 traits 实现了在 Formatter 中使用空白格式 {} 的功能。

pub trait Display {
    pub fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}
use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

let origin = Point { x: 0, y: 0 };

assert_eq!(format!("The origin is: {}", origin), "The origin is: (0, 0)");

使用 Traits 作为参数类型

在知道如何定义和实现 Traits 后,我们就可以探索如何使用 Traits 来定义接受许多不同类型的函数。这一切都与 Java 中的接口概念类似,也就是所谓的鸭子类型。事实上它们的使用场景也基本上是类似的。

我们定义一个 display 函数,它接收一个实现了 Display Traits 的参数 item。

pub fn display(item: &impl std::fmt::Display) {
    println!("My display item is {}", item);
}

item 的参数类型是 impl std::fmt::Display 而不是某个具体的类型(例如 Point),这样,任何实现了 Display Traits 的数据类型都可以作为参数传入该函数。


自动派生

Rust 编译器可以自动为我们的结构体实现一些 Traits,这种自动化技术被称作派生。例如,在编写代码的过程中最常见的一个需求就是将结构体输出的屏幕上,除了使用上节课提到的手工实现的 Display,也可以采用自动派生技术让 Rust 编译器自动帮你添加代码。

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 1, y: 2 };
    println!("{:?}", p);
}

Debug Trait 允许将数据结构使用 {:?} 格式进行格式化。

自动派生有一个前提是,该结构体中全部字段都实现了指定的 Trait,例如,上面例子中的 i32 和 i64 就已经实现了 Debug Trait。

现在,我们来为 Point 实现另一个 Trait:PartialEq。该特征允许两个数据使用 == 进行比较。

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 1, y: 2 };
    println!("{}", p1 == p2);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容