前言#
昨天我们说了下rust推荐的使用panic的场景
今天该学第十章了。
泛型#
前面已经多次提到过泛型了,另外在别的语言中你应该也有接触过,比如typescript。
在rust中,泛型可以用于function、struct以及enum等中。我们先来学下如何创建相应的泛型。
定义一个带有泛型的函数#
我们直接来看下例子
fn use_generic<T> (data: T) -> T {
data
}规范是放在函数名字之后()之前 ,用<>包裹中间的泛型,一般都是用缩写的大写字母。如果需要多个泛型,可以用,隔开。比如``。
好像没什么用?在这个方法中确实没什么用,不过我们可以来看下一个例子
fn largest_i32(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> &char {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest_i32(&number_list);
println!("The largest number is {}", result);
assert_eq!(*result, 100);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&char_list);
println!("The largest char is {}", result);
assert_eq!(*result, 'y');
}
稍微有那么一点长。
我们声明了两个function,largest_i32和largest_char,它们的作用都是用来寻找list中最大的那个元素,然后返回。
这俩函数唯一不同的地方就在于类型,元素的类型不同,但是由于强类型的问题,需要写两套代码,这就很难受。
这个时候我们就可以用泛型,我们来改下代码
fn largest<T>(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, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
assert_eq!(*result, 100);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
assert_eq!(*result, 'y');
}
这样是不是就少了很多重复代码
不过这里有个报错

不能用泛型来比较大小,这是必然的,因为连类型都不确定。
不过它有个help

这是一个trait,如果这个T标记了这个std::cmp::PartialOrd,那么就可以比较(当然不是所有类型都可以,而这里char和i32都实现了这个trait)。下一章就会说到trait,所以这里我们还是跳过。
从这上面的例子可以发现,泛型可以让我们节省下很多重复的逻辑代码。
定义一个带有泛型的构造体#
直接开看下例子
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 };
}
规范是定义在名字和{}之间
但是构造体有一个特点,就是类型可以不同,所以往往一个T并不能满足所有需求,比如
struct Point<T> {
x: T,
y: T,
}
fn main() {
let wont_work = Point { x: 5, y: 4.0 };
}这样就有问题了,x是i32,而y是f64。
所以这个时候得改下我们的泛型定义
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let wont_work = Point { x: 5, y: 4.0 };
}这样就没问题了
在枚举中使用泛型#
最典型的就是我们的Option和Result了。
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}就不多说了
在method中使用泛型#
来看下例子
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());
}开始有那么一点复杂了。。
这里有两个T,一个是impl后面的,一个是Point后面的,而 -> &T这个是impl后面的T,而Ponit后面的T实际上也是impl后面那个T,我们来改下impl后面那个T

结果发现俩T都不得劲了。
那到底啥意思呢?
我们再来看个例子
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
只有Point的类型可以使用这个distance_from_origin的method。
这就很舒服了,不同类型可以有不同的method,并且也不存在错用乱用的情况。当然,如果不确定类型就说明共用,所有类型都能用。
我们再来看个例子
struct Point<X1, Y1> {
x: X1,
y: Y1,
}
impl<X1, Y1> Point<X1, Y1> {
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}这个例子就有点复杂了。。。这个mixup方法实际上是将自己的x和另一个实例的y结合,生成一个新的实例,类型是Point。
有四个泛型X1,Y1,X2,Y2,其中Point后面的只能用这个X1,Y1,因为X2,Y2都是mixup这个方法的泛型。
也就是说这个同样是受限于作用域规则。
使用泛型的性能#
你应该会担心这玩意儿的性能问题(其实并没有),由于类型并不固定,所以会担心有runtime的花费(不好意思,也没有)。
实际上并没有runtime的花费,因为rust在编译的时候使用泛型来执行代码的单态化(monomorphization)使得runtime之前就确定下来了。这句话我翻译的应该不是很对。。。我把原文贴出来
Rust accomplishes this by performing monomorphization of the code using generics at compile time.啥意思呢?看着好高端的样子,实际上只是用一种特殊的方式执行这段代码然后确定下来类型而已,简单地说就是在编译的时候做类似runtime的操作确定下来类型(monomorphization是一个process也就是进程)。
我们来看个例子
#![allow(unused)]
fn main() {
let integer = Some(5);
let float = Some(5.0);
}这是一个Option的变体,它的值是一个泛型,在编译的时候发现了这段代码,monimorphization就开始运行啦,然后它跑完全部代码之后发现使用Option的枚举泛型有两个,一个是Option,一个是Option,这个时候它就会把Option拓展为i32和f64的类型。这样你这代码的类型就固定下来了,也就是不再存在T,而是i32和f64。
简单的说就像是语法糖,编译的时候会帮你编译成你手动去固定类型一样。如下面这段代码
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}看起来好像很6。
其实各位如果看过一些框架的编译器源码(比如说vue的sfc编译器),你就不会觉得这操作666了。
总结#
今天我们学了泛型,使用上和其他语言的泛型有点差别,但是不大。
最后如果觉得这篇文章对你有帮助的话,请务必点个赞,谢谢·~
参考#
- ^rust-generic-data-types https://doc.rust-lang.org/book/ch10-01-syntax.html
- ^rust-function-defining-with-generic https://doc.rust-lang.org/book/ch10-01-syntax.html#in-function-definitions
- ^rust-struct-defining-with-generic https://doc.rust-lang.org/book/ch10-01-syntax.html#in-struct-definitions
- ^rust-enum-defining-with-generic https://doc.rust-lang.org/book/ch10-01-syntax.html#in-enum-definitions
- ^rust-use-generic-in-method-defining
- ^rust-performance-of-code-using-generic https://doc.rust-lang.org/book/ch10-01-syntax.html#performance-of-code-using-generics
发布于 2022-12-14 22:47・IP 属地广东
