泛型、Trait、生命周期一、提取函数消除重复

fn main() {  let number_list = vec![34, 50, 25, 100, 65];  let mut largest = number_list[0];  for number in number_list {    if number > largest {      largest = number;    }  }    println!("The largest number is {}", largest);}

重复代码

  • 重复代码的危害:
    • 容易出错
    • 需求变更时需要在多处进行修改
  • 消除重复:提取函数
fn largest(list: &[i32]) -> i32 {  let mut largest = list[0];  for &item in list { // &item 解构    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 number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];  let result = largest(&number_list);  println!("The largest number is {}", result);}

消除重复的步骤

  • 识别重复代码
  • 提取重复代码到函数体中,并在函数签名中指定函数的输入和返回值
  • 将重复的代码使用函数调用进行替代

二、泛型泛型

  • 泛型:提高代码复用能力
    • 处理重复代码的问题
  • 泛型是具体类型或其它属性的抽象代替:
    • 你编写的代码不是最终的代码,而是一种模版,里面有一些“占位符”
    • 编译器在编译时将“占位符”替换为具体的类型
  • 例如:fn largest(list: &[T]) -> T {...}
  • 类型参数:
    • 很短,通常一个字母
    • CamelCase
    • T:type 的缩写

函数定义中的泛型

  • 泛型函数:
    • 参数类型
    • 返回类型
fn largest(list: &[T]) -> T {  let mut largest = list[0];  for &item in list {     if item > largest {  // 比较 报错 ToDo       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 number is {}", result);}

Struct 定义中的泛型

struct Point {  x: T,  y: T,}struct Point1 {  x: T,  y: U,}fn main() {  let integer = Point {x: 5, y: 10};  let float = Point(x: 1.0, y: 4.0);    let integer1 = Point1 {x: 5, y: 10.0};}
  • 可以使用多个泛型的类型参数
    • 太多类型参数:你的代码需要重组为多个更小的单元

Enum 定义中的泛型

  • 可以让枚举的变体持有泛型数据类型
    • 例如 OptionResult
enum Option {  Some(T),  None,}enum Result {  Ok(T),  Err(E),}fn main() {}

方法定义中的泛型

  • 为 struct 或 enum 实现方法的时候,可在定义中使用泛型
struct Point {  x: T,  y: T,}impl Point {  fn x(&self) -> &T {    &self.x  }}impl Point {  fn x1(&self) -> &i32 {    &self.x  }}fn main() {  let p = Point {x: 5, y: 10};  println!("p.x = {}", p.x());}
  • 注意:
    • 把 T 放在 impl 关键字后,表示在类型 T 上实现方法
      • 例如: impl Point
    • 只针对具体类型实现方法(其余类型没实现方法):
      • 例如:impl Point
  • struct 里的泛型类型参数可以和方法的泛型类型参数不同
struct Point {  x: T,  y: U,}impl Point {  fn mixup(self, other: Point) -> Point {    Point {      x: self.x,      y: other.y,    }  }}fn main() {  let p1 = Point {x: 5, y: 4};  let p2 = Point {x: "Hello", y: 'c'};  let p3 = p1.mixup(p2);    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);}

泛型代码的性能

  • 使用泛型的代码和使用具体类型的代码运行速度是一样的。
  • 单态化(monomorphization):
    • 在编译时将泛型替换为具体类型的过程
fn main() {  let integer = Some(5);  let float = Some(5.0);}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);}

三、Trait(上)Trait

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

定义一个 Trait

  • Trait 的定义:把方法签名放在一起,来定义实现某种目的所必需的一组行为。
    • 关键字:trait
    • 只有方法签名,没有具体实现
    • trait 可以有多个方法:每个方法签名占一行,以 ; 结尾
    • 实现该 trait 的类型必须提供具体的方法实现
pub trait Summary {  fn summarize(&self) -> String;}// NewsArticle// Tweetfn main() {}

在类型上实现 trait

  • 与为类型实现方法类似
  • 不同之处:
    • impl xxxx for Tweet {...}
    • 在 impl 的块里,需要对 Trait 里的方法签名进行具体的实现

lib.rs 文件

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)  }}

main.rs 文件

use demo::Summary;use demo::Tweet;fn main() {  let tweet = Tweet {    username: String::from("horse_ebooks"),    content: String::from("of course, as you probably already know, people"),    reply: false,    retweet: false,  };    println!("1 new tweet: {}", tweet.summarize())}

实现 trait 的约束

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

默认实现

lib.rs 文件

pub trait Summary {  // fn summarize(&self) -> String;  fn summarize(&self) -> String {    String::from("(Read more...)")  }}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)  }}

main.rs 文件

use demo::NewsArticle;use demo::Summary;fn main() {  let article = NewsArticle {    headline: String::from("Penguins win the Stanley Cup Championship!"),    content: String::from("The pittsburgh penguins once again are the best hockey team in the NHL."),    author: String::from("Iceburgh"),    location: String::from("Pittsburgh, PA, USA"),  };    println!("1 new tweet: {}", article .summarize())}
  • 默认实现的方法可以调用 trait 中其它的方法,即使这些方法没有默认实现。
pub trait Summary {  fn summarize_author(&self) -> String;    fn summarize(&self) -> String {    format!("Read more from {} ...", self.summarize_author())  }}pub struct NewsArticle {  pub headline: String,  pub location: String,  pub author: String,  pub content: String,}impl Summary for NewsArticle {  fn summarize_author(&self) -> String {    format!("@{}", self.author)  }}
  • 无法从方法的重写实现里面调用默认的实现

四、Trait(下)Trait 作为参数

pub fn notify(item: impl Summary) {  println!("Breaking news! {}", item.summarize());}
  • impl Trait 语法:适用于简单情况
  • Trait bound 语法:可用于复杂情况
    • impl Trait 语法是 Trait bound 的语法糖
pub fn notify(item: T) {  println!("Breaking news! {}", item.summarize());}
  • 使用 + 指定多个 Trait bound
pub fn notify(item: impl Summary + Display) {  println!("Breaking news! {}", item.summarize());}pub fn notify(item: T) {  println!("Breaking news! {}", item.summarize());}
  • Trait bound 使用where 子句
    • 在方法签名后指定 where 子句
pub fn notify(a: T, b: U) -> String {  format!("Breaking news! {}", a.summarize())}pub fn notify(a: T, b: U) -> Stringwhere T: Summary + Display, U: Clone + Debug,{  format!("Breaking news! {}", a.summarize())}

实现 Trait 作为返回类型

  • impl Trait 语法
pub fn notify1(s: &str) -> impl Summary {  NewsArticle {    headline: String::from("Penguins win the Stanley Cup Championship!"),    content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),    author: String::from("Iceburgh"),    location: String::from("Pittsburgh, PA, USA"),  }}
  • 注意: impl Trait 只能返回确定的同一种类型,返回可能不同类型的代码会报错

使用 Trait Bound 的例子

  • 例子:使用 Trait Bound 修复 largest 函数
fn largest(list: &[T]) -> T {  let mut largest = list[0].clone();    for item in list.iter() {    if item > &largest { // std::cmp::ParticalOrd      largest = item.clone();    }  }    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)}fn largest(list: &[T]) -> &T {  let mut largest = &list[0];    for item in list.iter() {    if item > &largest { // std::cmp::ParticalOrd      largest = item;    }  }    largest}fn main() {  let str_list = vec![String::from("hello"), String::from("world")];  let result = largest(&str_list);  println!("The largest word is {}", result);  }

使用 Trait Bound 有条件的实现方法

  • 在使用泛型类型参数的 impl 块上使用 Trait Bound,我们可以有条件的为实现了特定 Trait的类型来实现方法
use std::fmt::Display;struct Pair {  x: T,  y: T,}impl Pair {  fn new(x: T, y: T) -> Self {    Self {x, y}  }}impl Pair {  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)
fn main() {  let s = 3.to_string();}

五、生命周期(1/4)生命周期

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

生命周期 – 避免悬垂引用(dangling regerence)

  • 生命周期的主要目标:避免悬垂引用(dangling regerence)
fn main() {  {    let r;    {      let x = 5;      r = &x; // 报错    }    println!("r: {}", r);  }}

借用检查器

  • Rust编译器的借用检查器:比较作用域来判断所有的借用是否合法。
fn main() {  let x = 5;  let r = &x;    println!("r: {}", r);}

函数中的泛型生命周期

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(x: &'a str, y: &'a str) -> &'a str {  if x.len() > y.len() {    x  } else {    y  }}

六、生命周期(2/4)生命周期标注语法

  • 生命周期的标注不会改变引用的生命周期长度
  • 当指定了泛型生命周期参数,函数可以接收带有任何生命周期的引用
  • 生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期

生命周期标注 – 语法

  • 生命周期参数名:
    • 以 ‘ 开头
    • 通常全小写且非常短
    • 很多人使用 ‘a
  • 生命周期标注的位置:
    • 在引用的 & 符号后
    • 使用空格将标注和引用类型分开

生命周期标注 – 例子

  • &i32 // 一个引用
  • &’a i32 // 带有显示生命周期的引用
  • &’a mut i32 // 带有显示生命周期的可变引用
  • 单个生命周期标注本身没有意义

函数签名中的生命周期标注

  • 泛型生命周期参数声明在:函数名和参数列表之间的 里
  • 生命周期 ‘a 的实际生命周期是:x 和 y 两个生命周期中较小的那个
fn main() {  let string1 = String::from("abcd");  let result;  {    let string2 = String::from("xyz");    let result = longest(string1.as_str(), string2.as_str());  // 报错 string2  }   println!("The longest string is {}", result);}fn longest(x: &'a str, y: &'a str) -> &'a str {  if x.len() > y.len() {    x  } else {    y  }}

七、生命周期(3/4)深入理解生命周期

  • 指定生命周期参数的方式依赖于函数所做的事情
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(x: &'a str, y: &str) -> &'a str {  x}
  • 从函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配
  • 如果返回的引用没有指向任何参数,那么它只能引用函数内创建的值
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(x: &'a str, y: &str) -> &'a str {  let result = String::from("abc");  result.as_str()  // 报错 }fn longest(x: &'a str, y: &str) -> String {  let result = String::from("abc");  result}

Struct 定义中的生命周期标注

  • Struct 里可包括:
    • 自持有的类型
    • 引用:需要在每个引用上添加生命周期标注
struct ImportantExcerpt {  part: &'a str,}fn main() {  let novel = String::from("Call me Ishmael. Some years ago ...")    let first_sentence = novel.split('.')  .next()  .expect("Could not found a '.'");    let i = ImportantExcerpt {    part: first_sentence  };}

生命周期的省略

  • 我们知道:
    • 每个引用都有生命周期
    • 需要为使用生命周期的函数或Struct指定生命周期参数

生命周期省略规则

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

输入、输出生命周期

  • 生命周期在:
    • 函数/方法的参数:输入生命周期
    • 函数/方法的返回值:输出生命周期

生命周期省略的三个规则

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

生命周期省略的三个规则 – 例子

  • 假设我们是编译器:
  • fn first_word(s: &str) -> &str {
  • fn first_word(s: &'a str) -> &str {
  • fn first_word(s: &'a str) -> &'a str {
  • fn longest(x: &str, y: &str) -> &str{
  • fn longest(x: &'a str, y: &'b str) -> &str{ // 报错

八、生命周期(4/4)方法定义中的生命周期标注

  • 在 Struct 上使用生命周期实现方法,语法和泛型参数的语法一样
  • 在哪声明和使用生命周期参数,依赖于:
    • 生命周期参数是否和字段、方法的参数或返回值有关
  • Struct 字段的生命周期名:
    • 在 impl 后声明
    • 在 struct 名后声明
    • 这些声明周期是 Struct 类型的一部分
  • impl 块内的方法签名中:
    • 引用必须绑定于 Struct 字段引用的生命周期,或者引用是独立的也可以
    • 生命周期省略规则经常使得方法中的生命周期标注不是必须的
struct ImportantExcerpt {  part: &'a str,}impl ImportantExcerpt {  fn level(&self) -> i32 {    3  }    fn snnounce_and_return_part(&self, announcement: &str) -> &str {    println!("Attention please: {}", announcement);    self.part  }}fn main() {  let novel = String::from("Call me Ishmael. Some years ago ...")    let first_sentence = novel.split('.')  .next()  .expect("Could not found a '.'");    let i = ImportantExcerpt {    part: first_sentence,  };}

静态生命周期

  • ‘static 是一个特殊的生命周期:整个程序的持续时间。
    • 例如:所有的字符串字面值都拥有 ‘static 生命周期
    • let s: &'static str = "I have a static lifetime.";
  • 为引用指定 ’static 生命周期前要三思:
    • 是否需要引用在程序整个生命周期内都存活。

泛型参数类型、Trait Bound、生命周期

use std::fmt::Display;fn longest_with_an_announcement(x: &'a str, y: &'a str, ann: T) -> &'a strwhereT: Display,{  println!("Announcement! {}", ann);  if x.len() > y.len() {    x  } else {    y  }}fn main() {}

本文来自博客园,作者:QIAOPENGJUN,转载请注明原文链接:https://www.cnblogs.com/QiaoPengjun/p/17279765.html