Rust能力养成系列之(20) :泛型初步

前言

上一篇讲了类型系统及其重要性,本篇开始讲解泛型(Generic)。

 

泛型概论

从高级编程语言诞生之初,对力求完美的抽象能力的追求就一直是语言设计人员追求的目标。因此而催生了许多关于代码复用(code reuse)的想法。

首先是函数,函数允许开发者将命名实体中的指令序列置于特定的区域,也就是函数体内,之后可以重复调用该实体,并可以选择为每次的调用,接收相应类型的任何参数。可见,函数的出现降低了代码的复杂性,增强了可读性。然而,函数也只能做到这一点了。比如有个函数avg,用来计算给定的整数列表值的平均值,但后来有一个新的用例,需要计算浮点值列表的平均值,那么通常的解决方案是创建一个新函数,可以计算浮点数列表的平均值。接着,如果你又想接受一个双精度值列表呢?可能需要再编写一个函数。对于程序开发者来说,一遍又一遍的编写同一个函数,只是参数不同而已,这确实是在浪费宝贵的时间。

为了减少这种重复,语言设计人员想要创造一种表达代码的方法,这样avg函数就可以以一种接收多种类型参数的方式来编写,而这就是泛型函数(generic function),因此泛型编程的思想或泛型(generic programming, or generics)就诞生了。拥有可以接受多种类型的函数是泛型编程的特性之一,而且还可以在其他地方使用

https://note.youdao.com/yws/public/resource/2f44b68f8f346d0f304fdbb4f5cf8f76/xmlnote/70AF90CCD02A445297455E0B3B5A756F/56920

泛型。我们将在本系列章节中探讨所有这些问题。

泛型编程是一种只适用于静态类型编程语言的技术,首先出现在ML语言中,这是一种静态类型的函数式语言,而不是机器学习(Machine Learning)。像Python这样的动态语言使用duck typing,其中APIs根据其能做什么而不是什么来处理参数,所以并不依赖泛型。

https://note.youdao.com/yws/public/resource/2f44b68f8f346d0f304fdbb4f5cf8f76/xmlnote/F0767A860E194CB2A48F2E2713077D23/56923

泛型是语言设计特性的一部分,支持代码重用和“不要重复自己(Don't repeat yourself ,DRY)”原则。使用泛型技术,开发者可以编写算法,函数,方法,和类型的占位符类型,并为这些类型指定一个类型变量(以一个字母,通常是T, K,或V),在进行代码实例化之后,就可以告诉编译器填写真正的类型了。这些类型被称为泛型类或项(generic types or items)。像T这样的单个字母符号称为泛型类型参数(generic type parameters)。当使用或实例化任何泛型项的时候,这样的字母会被被具体的类型(如u32)替换。

注意一点:通过替换,这里的意思是每当一个泛型项与一个具体类型一起使用时,该代码的一个专门化副本会在编译时由类型变量T生成,并被替换为具体类型。在编译时生成具有具体类型的特定专门函数的过程称为单态化(monomorphization),这是与多态函数相反的过程。

让我们看一下Rust标准库中现有的一些泛型类型。

标准库中的Vec<T>类型是一个泛型类型,定义如下:

pub struct Vec<T> {
    buf: RawVec<T>,
    len: usize,
}

可以看到,Vec的类型签名在其名称后包含一个类型参数T,由尖括号< >包围,它的成员字段buf也是一个泛型类型,因此Vec本身必须是泛型的。如果我们在泛型类型Vec<T>上去掉T,即使我们在它的buf字段上加上T,还是得到以下错误:error[E0412]: cannot find type `T` in this scope。

这意味着T 需要成为Vec类型定义的一部分。因此,当我们表示一个Vec时,同时就是指在使用泛型Vec<T>,而当我们知道具体类型时,会考虑使用Vec<u64>之类的定义方式。接下来,我们看下如何创建泛型类型。

 

创建泛型类型概说

Rust允许我们将许多实体对象声明为泛型,比如结构、枚举、函数、特性、方法和实现区域(tructs, enums, functions, traits, methods, and implementation blocks)。一个共同点是泛型类型参数由一对< >方括号分隔并包含在括号中。在其中,可以放置任意数量的以逗号分隔的泛型类型参数。让我们来看看如何创建泛型,首先看看泛型函数。

 

泛型函数(Generic functions)

为了创建泛型函数,我们将泛型类型参数放在函数名的后面和圆括号之前,如下所示:

// generic_function.rs
 
fn give_me<T>(value: T) {
    let _ = value;
}
 
fn main() {
    let a = "generics";
    let b = 1024;
    give_me(a);
    give_me(b);
}

在上面的代码中,give_me是一个泛型函数,在它的名字后面有<T>, value形参是T类型的。在main中,我们可以用任何参数调用这个函数。在编译过程中,编译后的目标文件将包含该函数的两个专门副本。我们可以在生成的二进制对象文件中使用nm命令来确认,如下所示:

https://note.youdao.com/yws/public/resource/2f44b68f8f346d0f304fdbb4f5cf8f76/xmlnote/C5CD7888C6E34447847943023C35827A/57038

nm是GNU binutils包中的一个实用程序,用于查看已编译目标文件中的符号。通过传递nm到我们的二进制代码,我们用使用管道|和grep来作为give_me函数的前缀(这个操作在linux下进行)。如上所见,这里有该函数的两个副本,其中附加了随机id,以区分各种。其中一个参数为&str,另一个参数为i32,这是因为有两个调用具有不同类型的参数。

可以说,泛型函数是制造多态代码假象的一种cheap方法。我之所以说假象,是因为在编译之后,所有重复的代码都以具体类型作为参数。但是,也有一个缺点,即由于代码重复,编译后的目标文件的体积会增加,这显然与所使用的具体类型的数量成比例。在后面的部分中,当我们讨论特性(Trait)时,我们将看到多态性的真正形式——特性对象。尽管如此,在大多数情况下,通过泛型实现多态性是首选,因为没有运行时开销,就像Trait对象一样。只有当泛型不能满足解决方案,并需要在集合中存储一堆类型时,才应该使用Trait对象。当我们讲到特征对象时,会看到这些例子。

接下来,我们将看看如何使用结构和枚举泛型(structs and enums generic),首先研究如何声明,至于创建和使用这些类型将在后面的章节中介绍。

 

泛型类型(Generic types)

泛型结构(Generic structs):声明元组结构和普通结构,如下所示:

// generic_struct.rs
 
struct GenericStruct<T>(T);
 
struct Container<T> {
    item: T
}
 
fn main() {
    // stuff
}

泛型结构在结构的名称之后包含泛型类型参数,如上面的代码所示。正因如此,无论我们在代码中的哪一个地方表征这个结构,都必须要键入<T>部分和类型

泛型枚举(Generic structs):类似地,我们也可以创建泛型枚举:

// generic_enum.rs
 
enum Transmission<T> {
    Signal(T),
    NoSignal
}
 
fn main() {
    // stuff
}

可见,Transmission 枚举有一个名为Signal的变体,拥有一个泛型值,还有一个名为NoSignal的变体,没有赋值。

 

结语

本篇的内容还是很有张力的吧,下一篇,开始有关泛型的实现

 

主要参考和建议读者进一步阅读的文献

https://doc.rust-lang.org/book

Rust编程之道,2019, 张汉东

The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger

Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger

Beginning Rust ,2018,Carlo Milanesi

Rust Cookbook,2017,Vigneshwer Dhinakaran

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值