泛型
泛型编程是现代编程语言中重要的机制
C++是通过模板来实现泛型的,而C语言中是没有泛型的
泛型是用来表达抽象类型的机制,用于功能确定,但是数据类型不确定的类型
函数中的泛型
下面这个是没有泛型版本的取数组中最大值的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fn max(array: &[i32]) -> i32 { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] }
fn main() { let a = [2, 4, 6, 3, 1]; println!("max = {}", max(&a)); }
|
显然只能对i32类型的数字进行选择,如果使用泛型,就是这样的
1 2 3 4 5 6 7 8 9 10 11
| fn max<T>(array: &[T]) -> T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] }
|
我们只需要在函数后面加上<T>
即可表示一个泛型类型
结构体与枚举中的泛型
结构体和枚举中也是可以使用泛型的
1 2 3 4
| struct Point<T> { x: T, y: T }
|
rust中自带的option和result枚举就是泛型枚举
1 2 3 4 5 6 7 8 9
| enum Option<T> { Some(T), None, }
enum Result<T, E> { Ok(T), Err(E), }
|
结构体和枚举都是定义泛型的,而两者都可以实现方法,那么方法也应该可以实现泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct Point<T> { x: T, y: T, }
impl<T> Point<T> { fn x(&self) -> &T { &self.x } }
fn main() { let p = Point { x: 1, y: 2 }; println!("p.x = {}", p.x()); }
|
需要注意的是,impl之后必须要有泛型的定义,在imple之中也可以定义其他泛型的方法
1 2 3 4 5 6 7 8
| 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, } } }
|
特性(trait)
这个概念其实类似于接口,是一种行为规范,类似于C++中的接口类(但不完全相同),用于标识一个类有哪些方法
例如
1 2 3
| trait Descriptive { fn describe(&self) -> String; }
|
这就意味着,如果我们想要用这个trait,就必须要实现descirbe这个方法
例如
1 2 3 4 5 6 7 8 9 10
| struct Person { name: String, age: u8 }
impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } }
|
在这个例子中,我们实现了一个结构体(Persion),我们想让这个结构体拥有Descriptive
这个特性(trait),但是这个trait要求必须实现describe
方法,因此我们impl
实现了一下
格式就是
1
| impl <特性名> for <所实现的类型名>
|
Rust的一个结构体(一个类)可以有多个Trait
但是我们使用impl语句一次只能定义一个Trait
默认特性
我们可以给特性定义方法,这些方法称为默认方法
我们在给对象实现Trait的时候,可以不实现默认方法,这样就是直接用的
如果在给类实现Trait的时候,重写了对应的方法,那么调用的时候就是调用重写的方法
这里我们来讲一讲Rust的设计理念
在C++中,类可以定义成员变量和成员函数,但是到了Rust,把成员变量全部归给类结构体,只负责保存字段,而Trait则只负责定义行为,也就是成员函数
那我们在定义Trait的时候其实就类似于定义C++的接口类
既可以定义纯虚函数,也可以定义普通的虚函数
当我们给结构体赋予Trait的时候,就可以重写
这样做的好处是什么
显而易见的是Trait可以被复用了,多个struct可以实现同一个Trait
在Rust中是没有继承这个概念的,因为他不像传统的面向对象语言,在Rust中主要是通过组合和多态来实现功能的,又由于Trait比传统的多态的功能更加强大,所以Rust采用了这种思路
示例代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| trait Descriptive { fn describe(&self) -> String { String::from("[Object]") } }
struct Person { name: String, age: u8 }
impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } }
fn main() { let cali = Person { name: String::from("Cali"), age: 24 }; println!("{}", cali.describe()); }
|
Trait作为参数
有时候我们需要传递一个函数作为参数,例如回调函数,在C++中是通过传递函数对象实现的,但是在Rust中我们可以传递特性
例如
1 2 3
| fn output(object: impl Descriptive) { println!("{}", object.describe()); }
|
也可以通过泛型的语法糖,类似于C++中的特化模板
1 2 3 4
| fn output_two<T: Descriptive>(arg1: T, arg2: T) { println!("{}", arg1.describe()); println!("{}", arg2.describe()); }
|
如果特性作为参数时涉及多个特性,可以用+
来连接,例如
1 2
| fn notify(item: impl Summary + Display) fn notify<T: Summary + Display>(item: T)
|
这种使用方法只能用于标识类型,在语句中时不能使用的
有时候特性会非常多,可以用下面的方法来简化
1 2 3
| fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug
|
那我们最终实现的最大值就可以这样写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| trait Comparable { fn compare(&self, object: &Self) -> i8; }
fn max<T: Comparable>(array: &[T]) -> &T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i].compare(&array[max_index]) > 0 { max_index = i; } i += 1; } &array[max_index] }
impl Comparable for f64 { fn compare(&self, object: &f64) -> i8 { if &self > &object { 1 } else if &self == &object { 0 } else { -1 } } }
fn main() { let arr = [1.0, 3.0, 5.0, 4.0, 2.0]; println!("maximum of arr is {}", max(&arr)); }
|
特性做返回值
我们不能单独返回特性,只能够返回具有特性的实例,因此如果多个结构体实现了同一个特性,就需要确保返回值是相同类型的实例,并且也必须是实现了特性的对象才能返回
1 2 3 4 5 6
| fn person() -> impl Descriptive { Person { name: String::from("Cali"), age: 24 } }
|
给结构体实现方法
impl还可以给结构体赋予方法,并且可以要求实现的先后顺序
1 2 3 4 5
| struct A<T> {}
impl<T: B + C> A<T> { fn d(&self) {} }
|
这个意思是要给泛型A赋予一个方法d,但是这个方法必须要在实现了B和C之后才能实现