Rust(十)- 泛型、trait、生命周期

420 阅读8分钟

这大概是我学过的最恶心的一节了

提取函数 消除重复代码

重复代码

  • 重复代码的危害
    • 容易出错
    • 需求变更时需要在多处进行修改
  • 消除重复:提取函数
    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);
    
          let char_list = vec!['y', 'm', 'a', 'q'];
    
          let result = largest(&char_list);
          println!("The largest char is {}", result);
      }
    
    
  • 消除重复的函数
    • 识别重复代码
    • 提取重复代码到函数体中,并在函数签名中指定函数的输入和返回值
    • 将重复的代码使用函数调用进行替代

泛型

  • 泛型:提高代码复用能力
    • 处理重复代码的问题
  • 泛型是具体类型或其它属性的抽象代替
    • 你编写的代码不是最终的代码,而是一种模板,里面有一些“占位符”
    • 编译器在编译时将“占位符”替换为具体的类型
    • 类型参数
      • 很短,通常一个字母
      • camelcase
      • T:type的缩写
  • 函数定义中的泛型
    • 泛型函数:
      • 参数类型
      • 返回类型
          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);
      
              let char_list = vec!['y', 'm', 'a', 'q'];
      
              let result = largest(&char_list);
              println!("The largest char is {}", result);
          }
      
  • struct 定义中的泛型
    • 可以使用多个泛型的类型参数
      • 太多类型参数:你的代码需要重组为多个更小的单元
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());
  }
  • enum 定义中的泛型

    • 可以让枚举的变体持有泛型数据类型
      • 例如option< T >,Result<T, E>
          #![allow(unused)]
          fn main() {
          enum Option<T> {
              Some(T),
              None,
          }
          }
      
    • 注意:
      • 把T放在impl关键字后,表示在类型T上实现方法
      • 只针对具体类型实现方法(其余类型没实现方法):
    • stuct里的泛型类型参数可以和方法的泛型类型参数不同
  • 泛型代码的性能

    • 使用泛型的代码和使用具体类型的代码运行速度是一样的
    • 单态化(monomorphization)
      • 在编译时将泛型替换为具体类型的过程
  • Trait

    • trait 告诉 Rust 编译器
      • 某种类型具有哪些并且可以与其它类型共享的功能
    • trait: 抽象的定义共享行为
    • trait bounds(约束):泛型类型糁数指定为实现了特定行为的类型
    • trait 与其它语言的接口(interface)类型,但有些区别
  • 定义的一个trait

    • trait 的定义:把方法签名放在一起,来定义实现某种目的所必需的一组行为
      • 关键字:trait
      • 只有方法签名,没有具体实现
      • trait可以有多个方法:每个方法签名占一行,以;结尾
      • 实现该trait的类型必须提供具体的方法实现
      pub trait Summary {
          fn summarize(&self) -> String;
      }
      
    • 在类型上实现trait
      • 与为类型实现方法类似
      • 不同之处
        • 在impl的块里,需要对trait里的方法签名进行具体的实现
      pub trait Summary {
              fn summarize(&self) -> String;
          }
      
          pub struct NewsArticle {
              pub headline: String,
              pub location: String,
              pub author: String,
              pub content: String,
          }
      
          impl Summary for NewsArticle {
              fn summarize(&self) -> String {
                  format!("{}, by {} ({})", self.headline, self.author, self.location)
              }
          }
      
          pub struct Tweet {
              pub username: String,
              pub content: String,
              pub reply: bool,
              pub retweet: bool,
          }
      
          impl Summary for Tweet {
              fn summarize(&self) -> String {
                  format!("{}: {}", self.username, self.content)
              }
          }
      
      
  • 实现trait的约束

    • 可以在某个类型上实现某个trait的前提条件是:
      • 这个类型或这个tarit是在本地crate里定义的
    • 无法为外部类型来实现外部的trait
      • 这个限制是程序属性的一部分(也就是一致性)
      • 更具体地说是孤儿规则:之所以这样命名是因为父类型不存在
      • 此规则确保其他人的代码不能破坏您的代码,反之亦然
      • 如果没有这个规则,两个crate可以为同一类型实现同一个trait,Rust就不知道应该使用哪个实现了。
  • 默认实现

    pub trait Summary { fn summarize_author(&self) -> String;

         fn summarize(&self) -> String {
             format!("(Read more from {}...)", self.summarize_author())
         }
     }
    
     pub struct Tweet {
         pub username: String,
         pub content: String,
         pub reply: bool,
         pub retweet: bool,
     }
    
     impl Summary for Tweet {
         fn summarize_author(&self) -> String {
             format!("@{}", self.username)
         }
     }
    
    + 默认实现的方法可以调用trait中其它的方法, 即使这些方法没有默认实现。
    
  • trait 作为参数

    • Impl trait 语法:适用于简单情况
    • trait bound 语法:可用于复杂情况
      • impl trait 语法是 trait bound的语法糖
    • 使用 + 指定多个trait bound
    • trait bound 使用 where 子语句
    fn some_function<T, U>(t: &T, u: &U) -> i32
          where T: Display + Clone,
                U: Clone + Debug
      {
    
    
  • 实现trait作为返回类型

    • Impl trait 语法
    • 注意:impl trait 只能返回确定的同一类型,返回可能不同类型的代码会报错
    pub trait Summary {
      fn summarize(&self) -> String;
      }
    
      pub struct NewsArticle {
          pub headline: String,
          pub location: String,
          pub author: String,
          pub content: String,
      }
    
      impl Summary for NewsArticle {
          fn summarize(&self) -> String {
              format!("{}, by {} ({})", self.headline, self.author, self.location)
          }
      }
      pub struct Tweet {
          pub username: String,
          pub content: String,
          pub reply: bool,
          pub retweet: bool,
      }
    
      impl Summary for Tweet {
          fn summarize(&self) -> String {
              format!("{}: {}", self.username, self.content)
          }
      }
    
      fn returns_summarizable(switch: bool) -> impl Summary {
          if switch {
              NewsArticle {
                  headline: String::from(
                      "Penguins win the Stanley Cup Championship!",
                  ),
                  location: String::from("Pittsburgh, PA, USA"),
                  author: String::from("Iceburgh"),
                  content: String::from(
                      "The Pittsburgh Penguins once again are the best \
                       hockey team in the NHL.",
                  ),
              }
          } else {
              Tweet {
                  username: String::from("horse_ebooks"),
                  content: String::from(
                      "of course, as you probably already know, people",
                  ),
                  reply: false,
                  retweet: false,
              }
          }
      }
    
  • 使用 trait bound 的例子

    • 例子:使用trait Bound修复largest函数
    fn largest<T: PartialOrd + Copy>(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);
    
          let char_list = vec!['y', 'm', 'a', 'q'];
    
          let result = largest(&char_list);
          println!("The largest char is {}", result);
      }
    
  • 使用trait bound有条件的实现方法

    • 在使用泛型类型参数的impl块上使用trait bound,我们可以有条件的为实现了特别trait的类型来实现方法
    use std::fmt::Display;
    
      struct Pair<T> {
          x: T,
          y: T,
      }
    
      impl<T> Pair<T> {
          fn new(x: T, y: T) -> Self {
              Self { x, y }
          }
      }
    
      impl<T: Display + PartialOrd> Pair<T> {
          fn cmp_display(&self) {
              if self.x >= self.y {
                  println!("The largest member is x = {}", self.x);
              } else {
                  println!("The largest member is y = {}", self.y);
              }
          }
      }
    
    • 也可以为实现了其它trait的任意类型有条件的实现某个trait
    • 为满足trait bound的所有类型上实现trait叫做覆盖实现(blanket implementations)
    use std::fmt::Display;
    
      struct Pair<T> {
          x: T,
          y: T,
      }
    
      impl<T> Pair<T> {
          fn new(x: T, y: T) -> Self {
              Self { x, y }
          }
      }
    
      impl<T: Display + PartialOrd> Pair<T> {
          fn cmp_display(&self) {
              if self.x >= self.y {
                  println!("The largest member is x = {}", self.x);
              } else {
                  println!("The largest member is y = {}", self.y);
              }
          }
      }
    
    
  • 生命周期

    • rust的每个引用都有自己的生命周期
    • 生命周期:引用保持有效的作用域
    • 大多数情况:生命周期是隐式的、可被推断的
    • 当引用的生命周期可能以不同的方式互相关联时:手动标注生命周期
  • 生命周期 - 避免悬垂引用(dangling refernce)

    • 生命周期的主要目标:避免悬垂引用(dangling refernce)
    • 时行赋值引用时,避免生命周期不同时,过短则报错
  • 函数中的泛型生命周期

    fn main() {
          let string1 = String::from("abcd");
          let string2 = "xyz";
    
          let result = longest(string1.as_str(), string2);
          println!("The longest string is {}", result);
      }
    
      fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
          if x.len() > y.len() {
              x
          } else {
              y
          }
      }
    
    
  • 生命周期标注语法

    • 生命周期的标注不会改变引用的生命周期长度
    • 当指定了泛型生命周期参数, 函数可以接收带有任何生命周期的引用
    • 生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期
    • 生命周期参数名:
      • 以‘开头
      • 通常全小写且非常短
      • 很多人使用 ’a
    • 泛型生命周期标注的位置
      • 在引用的&符号后
      • 使用空格将标和引用类型分开
    • 泛型生命周期-例子
      • &i32 // 一个引用
      • & ‘a i32 // 带有显式生命周期的引用
      • & ‘a mut i32 // 带有显式生命周期的可变引用
    • 单个生命周期标注本身没有意义
  • 函数签名中的生命周期标注

    • 泛型生命周期参数声明在: 函数名和参数列表之间的<>里
    • 生命周期‘a的实际生命周期是:x和y两个生命周期中较小的那个
  • 深入理解生命周期

    • 指定生命周期参数的方式依赖于函数所做的事情
    • 从函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配
    • 如果返回的引用没有指向任何参数,那么它只能引用函数内创建的值
      • 这就是悬垂引用:该值在函数结束时就走出了作用域
    • 生命周期的省略
      • 每个引用都有生命周期
      • 需要为使用生命周期的函数或struct指定生命周期参数
      • 这他妈是日了鬼了吗, 啥都要生命周期
  • 生舍周期省略规则

    • 在Rust引用分析中所编入的模式称为生命周期省略规则。
      • 这些规则无需开发者来遵循
      • 它们是一些特殊情况,由编译器来考虑
      • 如果你的代码符合这些情况,那么就无需显式标注生命周期
    • 生命周期省略规则不会提供完整的推断
    • 如果应用规则后,引用的生命周期仍然模糊不清 -> 编译错误
    • 解决办法:添加生命周期标注,表明引用间的相互关系
  • 输入、输出生命周期

    • 生命周期在:
      • 函数/方法的参数:输入生命周期
      • 函数/方法的返回值:输出生命周期
  • 生命周期省略的三个规则

    • 编译器使用3个规则在没有显式标生命周期的情况下,来确定引用的生命周期
      • 规则1应用于输入生命周期
      • 规则2、3应用于输出生命周期
      • 如果编译器应用完3个规则之后,仍然有无法确定生命周期的引用-》报错
      • 这些规则适用于fn定义和Impl块
    • 规则1: 每个引用类型的参数都有自己的生命周期
    • 规则2:如果只有1个输入生命周期参数,那么该生命mfka德维尔被赋给所有的输出生命周期参数
    • 规则3:如果有多个输入生命周期参数, 但其中一个是&self或 &mut self(是方法),那么self的生命周期会被赋给所有的输出生命周期参数
  • 静态生命周期

    • ‘static 是一个特殊的生命周期:整个程序的持续时间

      • 例如: 所有的字符串字面值都拥有‘static生命周期
        • let s: &'static str = 'i have a static lifetime';
    • 为引用指定‘static'生命周期前要三思:

      • 是否需要引用在程序整个生命周期内都存活
    • 泛型参数类型、trait bound、生命周期