0 前言

0.0 重复的代码

  1. fn main() {
  2. let number_list = vec![34, 50, 25, 100, 65];
  3. let mut largest = number_list[0];
  4. for number in number_list {
  5. if number > largest {
  6. largest = number;
  7. }
  8. }
  9. println!("The largest number is {}", largest);
  10. let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
  11. let mut largest = number_list[0];
  12. for number in number_list {
  13. if number > largest {
  14. largest = number;
  15. }
  16. }
  17. println!("The largest number is {}", largest);
  18. }

0.1 使用函数抽象重复的部分

  1. fn largest(list: &[i32]) -> i32 {
  2. let mut largest = list[0];
  3. for &item in list.iter() {
  4. if item > largest {
  5. largest = item;
  6. }
  7. }
  8. largest
  9. }
  10. fn main() {
  11. let number_list = vec![34, 50, 25, 100, 65];
  12. let result = largest(&number_list);
  13. println!("The largest number is {}", result);
  14. let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
  15. let result = largest(&number_list);
  16. println!("The largest number is {}", result);
  17. }
  • 遗留问题:如果有两个函数,一个用于寻找元素类型为i32的切片中的最大值;另一个寻找元素类型为char的切片中的最大值,这时候该如何消除重复?

    1 泛型

    1.1 函数中的泛型

    1.1.1 重复的函数

  • 下面代码中的largest_i32largest_char函数仅参数和返回值的类型不同,函数体完全相同,这是一种重复

  1. fn largest_i32(list: &[i32]) -> i32 {
  2. let mut largest = list[0];
  3. for &item in list.iter() {
  4. if item > largest {
  5. largest = item;
  6. }
  7. }
  8. largest
  9. }
  10. fn largest_char(list: &[char]) -> char {
  11. let mut largest = list[0];
  12. for &item in list.iter() {
  13. if item > largest {
  14. largest = item;
  15. }
  16. }
  17. largest
  18. }
  19. fn main() {
  20. let number_list = vec![34, 50, 25, 100, 65];
  21. let result = largest_i32(&number_list);
  22. println!("The largest number is {}", result);
  23. assert_eq!(result, 100);
  24. let char_list = vec!['y', 'm', 'a', 'q'];
  25. let result = largest_char(&char_list);
  26. println!("The largest char is {}", result);
  27. assert_eq!(result, 'y');
  28. }

1.1.2 使用泛型避免重复

  1. #![allow(dead_code)]
  2. use std::cmp::PartialOrd;
  3. fn largest<T>(list :&[T]) -> T where T: Clone + PartialOrd{
  4. let mut max = &list[0];
  5. for cur in list.iter(){
  6. if cur > max{
  7. max = cur;
  8. }
  9. }
  10. max.clone()
  11. }
  12. // 注意: 两种写法的差别
  13. fn largest2<T>(list :&[T]) -> T where T: Clone + PartialOrd{
  14. let mut max = list[0].clone();
  15. for cur in list.iter(){
  16. // 写成 cur > &max 也可以,但是 cur > max 不行
  17. // 因为 > 要求调用 PartialOrd 的 fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>; 方法
  18. // 第二个参数必须是引用类型
  19. if *cur > max{
  20. max = cur.clone();
  21. }
  22. }
  23. max
  24. }
  25. pub fn main() {
  26. let number_list = vec![34, 50, 25, 100, 65];
  27. let result = largest(&number_list);
  28. println!("The largest number is {}", result);
  29. assert_eq!(result, 100);
  30. let char_list = vec!['y', 'm', 'a', 'q'];
  31. let result = largest(&char_list);
  32. println!("The largest char is {}", result);
  33. assert_eq!(result, 'y');
  34. }

1.2 结构体中的泛型

1.2.1 单个泛型类型

  1. // 要求x,y的类型相同
  2. struct Point<T> {
  3. x: T,
  4. y: T,
  5. }
  6. fn main() {
  7. let integer = Point { x: 5, y: 10 };
  8. let float = Point { x: 1.0, y: 4.0 };
  9. // 这里不正确: x,y的类型不一致
  10. // let wont_work = Point { x: 5, y: 4.0 };
  11. }

1.2.2 多个泛型类型

  1. struct Point<T, U> {
  2. x: T,
  3. y: U,
  4. }
  5. fn main() {
  6. let both_integer = Point { x: 5, y: 10 };
  7. let both_float = Point { x: 1.0, y: 4.0 };
  8. // 允许x,y的类型不同
  9. let integer_and_float = Point { x: 5, y: 4.0 };
  10. }

1.3 枚举中的泛型

  • 标准库中的std::option::Optionstd::result::Result枚举类型使用了泛型
  1. pub enum Option<T> {
  2. None,
  3. Some(T),
  4. }
  1. pub enum Result<T, E> {
  2. Ok(T),
  3. Err(E),
  4. }

1.4 方法定义中的泛型

  1. struct Point<T> {
  2. x: T,
  3. y: T,
  4. }
  5. // impl 后面的 <T> 是必须的,这样编译器才知道后面的 T 表示的是泛型类型,而不是具体类型
  6. impl<T> Point<T> {
  7. fn x(&self) -> &T {
  8. &self.x
  9. }
  10. }
  11. // 这里 impl 后面没有 <T>,则编译器知道后面的 f32 是一个具体类型,而不是泛型类型
  12. // 这表示 Point<f32>具有 distance_from_origin() 方法,而其他 T 不等于 f32 的 Point 类型没有这个方法
  13. impl Point<f32> {
  14. fn distance_from_origin(&self) -> f32 {
  15. (self.x.powi(2) + self.y.powi(2)).sqrt()
  16. }
  17. }
  18. fn main() {
  19. let p = Point { x: 5, y: 10 };
  20. println!("p.x = {}", p.x());
  21. }

1.4.1 多种泛型参数的混合使用

  1. struct Point<T, U> {
  2. x: T,
  3. y: U,
  4. }
  5. // T,U 泛型参数用于结构体
  6. impl<T, U> Point<T, U> {
  7. // V,W 泛型参数仅用于 mixup 方法
  8. fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
  9. Point {
  10. x: self.x,
  11. y: other.y,
  12. }
  13. }
  14. }
  15. fn main() {
  16. let p1 = Point { x: 5, y: 10.4 };
  17. let p2 = Point { x: "Hello", y: 'c'};
  18. let p3 = p1.mixup(p2);
  19. println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
  20. }

1.5 泛型的单态化

  • 编译器通过对泛型代码进行单态化(monomorphization)来保证性能
  • 单态化就是将泛型类型替换成具体的类型,对每种具体类型,都生成一个不带泛型的特定的单态化类型
  1. let integer = Some(5);
  2. let float = Some(5.0);
  • 上述代码对Option<T>使用了两种具体类型:i32f64,则编译器会生成两种不带泛型的单态化类型:
  1. enum Option_i32 {
  2. Some(i32),
  3. None,
  4. }
  5. enum Option_f64 {
  6. Some(f64),
  7. None,
  8. }

2 用特性定义共享的行为

2.1 定义特性

  • 特性:将方法签名组合起来,定义一个实现某些目的所必须的行为集合
  1. pub trait Flyable{
  2. fn fly(&self,src: String,dst: String);
  3. }

2.2 实现特性

  1. struct Plane{
  2. }
  3. impl Flyable for Plane{
  4. fn fly(&self,src:String,dst:String){
  5. println!("民用飞机装载乘客从{}飞往{}",src,dst);
  6. }
  7. }
  • 孤儿规则:特性或者类型中至少一个位于本地包时,才可以为类型实现特性
  • 无法为其他包中的类型实现其他包中的特性

2.3 默认实现

  • 可以在定义特性的时候为方法提供默认实现
  • 可以在默认实现中调用其他没有默认实现的方法
  • 特性方法有默认实现时,为类型实现特性时,可以仅仅声明实现特性,而不必给出方法,从而使用默认实现;当然也可以覆盖默认实现
  1. pub trait Summarizable {
  2. fn author_summary(&self) -> String;
  3. fn summary(&self) -> String {
  4. format!("(Read more from {}...)", self.author_summary())
  5. }
  6. }

2.4 特性限定

  • 规定用作泛型类型的具体类型,必须实现某些特性
  1. fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {}
  2. fn some_function<T,U>(t:T,u:U) -> i32 where T:Display + Clone,U:Clone + Debug{}

2.5 用特性限定有条件地实现方法

  1. use std::fmt::Display;
  2. struct Pair<T> {
  3. x: T,
  4. y: T,
  5. }
  6. impl<T> Pair<T> {
  7. fn new(x: T, y: T) -> Self {
  8. Self {x,y}
  9. }
  10. }
  11. // 只有泛型类型T实现了Display和PartialOrd时,才实现cmp_display方法
  12. impl<T: Display + PartialOrd> Pair<T> {
  13. fn cmp_display(&self) {
  14. if self.x >= self.y {
  15. println!("The largest member is x = {}", self.x);
  16. } else {
  17. println!("The largest member is y = {}", self.y);
  18. }
  19. }
  20. }

2.6 总括实现

  • 总括实现(blanket implementation):对实现了某些特性的所有类型实现一些方法
  1. trait Flyable{
  2. fn fly(&self,src: String,dst: String);
  3. }
  4. trait Weapon{
  5. fn kill(&self,enmy: String);
  6. }
  7. trait AirAttack{
  8. fn bomb(&self,base: String,dst:String,enmy: String);
  9. }
  10. // 总括实现:实现了Flyable和Weapon的类型就可以实现AirAttack
  11. impl<T> AirAttack for T where T:Flyable + Weapon{
  12. fn bomb(&self,base:String,dst:String,enmy:String){
  13. self.fly(base,dst);
  14. self.kill(enmy);
  15. }
  16. }

3 生命周期与引用有效性

3.1 生命周期可避免悬垂引用

  1. {
  2. let r;// 只声明变量,不初始化是允许的。但是:如果在初始化之前使用它,则出现编译错误。
  3. {
  4. let x = 5;
  5. r = &x; // 发生借用
  6. }
  7. // 被借用的值x超出作用域,已经失效,这里r成为悬垂指针,出现编译错误。
  8. println!("r: {}", r);
  9. }
  • 上面示例中,记变量r的生命周期为'a,它的范围为外层的大括号内部;记变量x的生命周期为'b,它的范围为内层的大括号内部。
  • 编译时,编译器检查变量的生命周期:被引用变量x的生命周期'b,比引用它的变量r的生命周期'a小,通常记作:'b : 'a
  • 编译器拒绝编译程序:被引用对象的生命周期,比引用者的生命周期短
  • 编译器中执行上述生命周期规则检查的组件称作借用检查器(borrow checker)
  • 下面是一个正确的例子:被引用对象x的生命周期'b,比引用对象r的生命周期'a长。
  1. {
  2. let x = 5; // -----+-- 'b
  3. // |
  4. let r = &x; // --+--+-- 'a
  5. // | |
  6. println!("r: {}", r); // | |
  7. // --+ |
  8. }

3.2 函数中的生命周期

  1. fn main() {
  2. let string1 = String::from("abcd");
  3. let string2 = "xyz";
  4. let result = longest(string1.as_str(), string2);
  5. println!("The longest string is {}", result);
  6. }
  7. fn longest(x: &str, y: &str) -> &str {
  8. if x.len() > y.len() {
  9. x
  10. } else {
  11. y
  12. }
  13. }
  • 上述代码中的longest函数无法通过编译,错误为:缺少生命周期注解
  • 编译器无法在编译时确定返回值的生命周期是与参数x相同,还是与参数y相同

    3.2.1 生命周期注解

  • 语法:&'生命周期名称 类型名称,如&'a str

  • 生命周期仅用于引用类型
  • 生命周期名称通常是一个小写字母
  • 生命周期注解不改变引用的生命周期,只是用于指示多个引用的生命周期之间的关系
  • 所以,单个生命周期注解没有什么意义,因为没有涉及多个引用,不能指示多个引用的生命周期之间的关系

    3.2.2 函数签名中的生命周期注解

  • 上面的longest函数改成下面这样就可以通过编译了:

  1. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  2. if x.len() > y.len() {
  3. x
  4. } else {
  5. y
  6. }
  7. }
  • 上述签名指示两个参数的生命周期、以及返回值的生命周期,是一样长的
  • 生命周期注解只出现在函数签名中,而不用在函数体中,因为编译器可以分析函数体中的代码,不需要任何帮助
  • 调用函数时,表示生命周期的泛型参数'a被替换成两个参数xy的生命周期的重叠部分
  • 这样,返回值的生命周期等于xy的生命周期重叠部分,返回值一定在xy中生命周期较短的那个失效之前有效

3.2.2.1 正确调用示例

  1. fn main() {
  2. let string1 = String::from("long string is long");
  3. {
  4. let string2 = String::from("xyz");
  5. let result = longest(string1.as_str(), string2.as_str());
  6. println!("The longest string is {}", result);
  7. }
  8. }
  • 上述代码中,调用longest时传入的生命周期注解'a是两个参数生命周期的重叠部分,即内层大括号范围
  • 调用完成后,返回值result的生命周期范围是内层大括号中的后两个语句,比'a小,所以代码有效,可通过编译

3.2.2.2 错误的调用示例

  1. fn main() {
  2. let string1 = String::from("long string is long");
  3. let result;
  4. {
  5. let string2 = String::from("xyz");
  6. result = longest(string1.as_str(), string2.as_str());
  7. }
  8. println!("The longest string is {}", result);
  9. }
  • 上述代码中,调用longest时传入的生命周期注解'a是两个参数生命周期的重叠部分,即内层大括号范围
  • 调用完成后,返回值的生命周期范围等于'a,即内层大括号范围:返回值在离开内层大括号时失效
  • 最后的语句试图通过result使用返回值,会发生错误。错误为:string2的生命周期不够久
  • 实际上longest返回的是string1的引用,其生命周期比result
    • 然而,编译器无法执行动态检查,无法知道函数返回的是string1的引用
    • 编译器只能执行静态检查,认为传入的生命周期'a的范围为内层大括号,返回值的生命周期范围也是内层大括号

3.2.3 深入理解生命周期

  • 指定生命周期注解的方式与函数功能相关,比如说:
  1. fn longest<'a>(x: &'a str, y: &str) -> &'a str {
  2. x
  3. }
  • 上述代码总是返回第一个参数,所以不需要为第二个参数指定生命周期注解,因为返回值的生命周期与第二个参数没有关系
  • 返回引用类型时,返回值的生命周期必须与某个输入参数相关
  • 如果不与输入参数相关,则引用指向函数内部创建的资源,函数返回后,返回的引用将成为悬垂指针,使得代码通不过编译
  • 需要返回函数内部创建的资源的引用时,最好的解决方案是返回一个所有权类型,而不是引用类型

3.3 结构体中的生命周期

  • 对于结构体含有的每个引用类型的字段,必须给出生命周期注解
  1. struct ImportantExcerpt<'a> {
  2. part: &'a str,
  3. }
  4. fn main() {
  5. let novel = String::from("Call me Ishmael. Some years ago...");
  6. let first_sentence = novel.split('.').next().expect("Could not find a '.'");
  7. let i = ImportantExcerpt { part: first_sentence };
  8. }

3.4 省略生命周期注解

  1. fn first_word(s: &str) -> &str {
  2. let bytes = s.as_bytes();
  3. for (i, &item) in bytes.iter().enumerate() {
  4. if item == b' ' {
  5. return &s[0..i];
  6. }
  7. }
  8. &s[..]
  9. }
  • 上述函数签名省略了生命周期注解,但是可以通过编译。但在早期版本的Rust编译器中,生命周期注解是不能省略的。
  • 后来Rust开发团队发现了一些明确的模式,在这些模式下可以省略生命周期注解,编译器可以推导出生命周期。
  • 这些模式被添加到编译器中,形成生命周期省略规则(lifetime elision rules)。未来可能会发现更多可以省略生命周期注解的模式,从而可以在更多情况下省略生命周期注解。
  • 省略规则不能提供完整的判断,如果在明确遵循省略规则的情况下,仍然不能推导出生命周期,编译仍然会出错,此时还是需要增加生命周期注解。

3.4.1 生命周期注解省略规则

  • 输入生命周期:函数/方法参数的生命周期
  • 输出生命周期:函数/方法返回值的生命周期
  1. 每个引用类型的参数有自己的生命周期
  2. 如果只有一个输入生命周期,则输出生命周期等于输入生命周期
  3. 如果有一个输入参数为&self或者&mut self,则输出生命周期等于self的生命周期。这一条仅适用于方法。

3.4.1.1 示例1

  1. digraph demo1{
  2. node [margin=0 fontcolor=blue fontsize=16 width=3.5 shape=box]
  3. a [label="fn first_word(s: &str) -> &str {"]
  4. b [label="fn first_word(s: &'a str) -> &str {"]
  5. c [label="fn first_word(s: &'a str) -> &'a str {"]
  6. a -> b[label="规则1:每个输入参数有不同的生命周期" fontsize="12"]
  7. b -> c[label="规则2:如果只有一个参数,则返回值的生命周期等于参数的生命周期" fontsize="12"]
  8. }

3.4.1.2 示例2
  1. digraph demo1{
  2. node [margin=0 fontcolor=blue fontsize=16 width=4 shape=box]
  3. a [label="fn longest(x:&str,y:&str) -> &str {"]
  4. b [label="fn longest(x:&'a str,y:&'b str) -> &str {"]
  5. c [label="编译错误:无法确定返回值的生命周期" fontcolor="red"]
  6. a -> b[label="规则1:每个输入参数有不同的生命周期" fontsize="12"]
  7. b -> c[label="规则2:无法应用,因为有多个参数;规则3:无法应用,因为不是方法,没有self引用参数" fontsize="12"]
  8. }

3.5 方法定义中的生命周期注解

3.5.1 示例1

  1. impl<'a> ImportantExcerpt<'a> {
  2. fn level(&self) -> i32 {
  3. 3
  4. }
  5. }
  • 根据规则3,上述level方法中可省略生命周期注解
  • impl<'a> ImportantExcerpt<'a>中的生命周期注解也可以省略,因为方法的返回值不是引用类型,不需要生命周期

3.5.2 示例2

  1. impl<'a> ImportantExcerpt<'a> {
  2. fn announce_and_return_part(&self, announcement: &str) -> &str {
  3. println!("Attention please: {}", announcement);
  4. self.part
  5. }
  6. }
  • 根据规则3,上述announce_and_return_part方法中可省略生命周期注解,返回值的生命周期等于self的生命周期
  • impl<'a> ImportantExcerpt<'a>中的生命周期注解不可以省略,因为返回值是引用类型,需要使用这个生命周期

3.6 静态生命周期

  • 静态生命周期用&'static表示
  • 字面字符串的生命周期都是&'static
  • 全局变量(也称作“静态变量”,在函数/方法体外定义 )的生命周期也是&'static
  • 通常不需要&'static,多数情况下,问题可能是想创建悬垂引用,或者可用的生命周期不匹配,此时应该解决问题,而不是使用&'static

3.7 泛型类型参数、特性限定和生命周期

  • 将泛型类型参数、特性限定、生命周期结合在一起的写法如下
  1. use std::fmt::Display;
  2. fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str where T: Display
  3. {
  4. println!("Announcement! {}", ann);
  5. if x.len() > y.len() {
  6. x
  7. } else {
  8. y
  9. }
  10. }