rust学习笔记——泛型、Trait和生命周期

目录

泛型

what

when

how

函数

结构体

枚举

方法

泛型代码的性能

Trait

what

when

how

定义

为类型实现trait

默认实现

Trait Bound语法

生命周期

what

when

how

生命周期省略(Lifetime Elision)

函数

结构体

方法定义

静态生命周期

结合泛型、trait和生命周期


泛型

what

高效处理重复概念的工具之一

具体类型的抽象(因此所有具体类型可以出现的语法位置都可以用泛型)

when

泛型可以用于定义函数、结构体、枚举和方法,用于对于不同类型相同行为的抽象

how

函数
fn largest<T>(list: &[T]) -> &T {
结构体
struct Point<T, U> {
    x: T,
    y: U,
}
枚举
enum Result<T, E> {
    Ok(T),
    Err(E),
}
方法

注意点:

  1. impl<T>这里的T对应于结构体声明中的泛型类型参数,表示实现对所有类型实例均适用,对比特定的实现这里就不需要<T>
  2. 方法参数中用到的泛型和函数是一样的
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

impl Point<f32> {
     fn distance_from_origin(&self) -> f32 {
         (self.x.powi(2) + self.y.powi(2)).sqrt()
     }
}
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,
         }
     }
}

泛型代码的性能

rust通过编译时进行泛型代码的单态化(monomorphization),相当于在编译时进行用到的具体类型的代码自动生成,使得泛型没有运行时开销

Trait

what

定义泛型共同行为的方法(和泛型紧密相关,用于限制泛型行为,泛型可以出现的位置都可以用trait修饰)

when

trait最终还是落脚到类型,相当于对泛型的限制,是具有某些相同行为的类型形成的子集

从形式上讲,所有具体类型可以出现的地方几乎都可以使用trait作为类型

how

定义
pub trait Summary {
     fn summarize(&self) -> String;
}
为类型实现trait

注意点:

  1. 孤儿规则、相干性:只有在trait或类型两者至少有一个属于当前crate时,才能对类型实现trait(保证其他人编写的代码不会破坏你的代码)
impl Summary for NewsArticle {
     fn summarize(&self) -> String {
         format!("{}, by {} ({})", self.headline, self.author, self.location)
     }
}

impl Summary for Tweet {
     fn summarize(&self) -> String {
         format!("{}: {}", self.username, self.content)
     }
}
默认实现

注意点:

  1. 默认实现允许调用相同trait中的其他方法,哪怕这些方法没有默认实现(更细粒度的重复代码消除,更加灵活)
  2. 无法从相同方法的重载实现中调用默认方法
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 Bound语法

注意点:

  1. impl Trait是trait bound语法的语法糖
  2. 在一个函数签名中,强制两个参数为实现同一trait的相同具体类型时,只能使用trait bound
    pub fn notify(item1: &impl Summary, item2: &impl Summary) {
    
    pub fn notify<T: Summary>(item1: &T, item2: &T) {
    
  3. 通过+指定多个
    pub fn notify(item: &(impl Summary + Display)) {
    
    pub fn notify<T: Summary + Display>(item: &T) {
  4. 通过where简化(使得函数签名更加清晰)
    fn some_function<T, U>(t: &T, u: &U) -> i32
    where
         T: Display + Clone,
         U: Clone + Debug,
    {
  5. 当返回值为trait时,只适用于返回单一类型的情况(不能编译的原因后续补上)
  6. 将trait理解为泛型的类型(泛型的子集),通过使用带有trait bound的泛型参数的impl块,可以有条件地只为哪些实现了特定trait的类型实现方法
    impl<T: Display + PartialOrd> Pair<T> {
  7. 对任何实现了特定trait的类型有条件地实现trait
    impl<T: Display> ToString for T {

生命周期

what

一类允许我们向编译器提供引用如何相互关联的泛型

  1. 生命周期是主要用于避免悬垂引用的泛型(是泛型,和泛型语法一致)
  2. 生命周期是标注给借用检查器看的,随着借用检查器(通过在编译时比较作用域来确保借用的有效性)的升级(推断能力的增强),生命周期标注会越来越少
  3. 生命周期的标注不改变引用的生命周期,只是注明引用之间的关系和约束

when

  1. 函数和方法不适用生命周期省略规则时
  2. 结构体包含引用时

how

生命周期省略(Lifetime Elision)

省略规则是借用检查器升级的结果,是对一些常见的生命周期注解模式的匹配

输入生命周期:函数或方法的参数的生命周期

输出生命周期:返回值的生命周期

规则:

  1. 编译器为每一个引用参数都分配一个生命周期参数(几个就有几个不同的)
  2. 如果只有一个输入生命周期参数,那么将其赋予所有输出生命周期参数
  3. 如果方法有多个输入生命周期参数并且其中一个参数是&self或&mut self,说明是个对象的方法,那么所有输出生命周期参数被赋予self的生命周期(如果关联的不是&self则手动标注,但似乎这种情况很怪)
函数

解释:

  1. 我们标注返回引用的生命周期是以函数的逻辑(编译时的返回值)为依据的,错误的标记会导致编译错误。
  2. 这里泛型生命周期'a的具体生命周期等同于x和y的作用域的交集,同时我们用'a标记了返回的引用,因此返回的引用会在这个交集的作用域内保持有效(告诉borrow checker)
  3. 借用检查器会根据标注的生命周期检查是否存在悬垂引用
  4. 如果一个函数返回引用,但是没有和参数中的某个引用建立生命周期的关联关系,那么必然会存在悬垂引用,因为只能指向一个函数内部创建的值
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
     if x.len() > y.len() {
         x
     } else {
         y
     }
}
结构体

含义:ImportantExcerpt的实例不能比其part字段中的引用存在得更久

struct ImportantExcerpt<'a> {
     part: &'a str,
}
方法定义

解释:

  1. 首先,impl<'a> ImportantExcerpt<'a>这一写法和泛型是一致的,ImportantExcerpt<'a>中的<'a>是结构体类型的一部分,是一个整体,而'a参数在使用时需要在之前声明,即impl<'a>
  2. 对于第一个方法,使用了生命周期省略的第一条规则
  3. 对于第二个方法,使用了生命周期省略第一、三条规则
  4. 方法中可以有独立的生命周期泛型参数
struct ImportantExcerpt<'a> {
     part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
     fn level(&self) -> i32 {
         3
     }
}

impl<'a> ImportantExcerpt<'a> {
     fn announce_and_return_part(&self, announcement: &str) -> &str {
         println!("Attention please: {}", announcement);
         self.part
     }
}
静态生命周期

解释:

  1. 'static生命周期能够存活于整个程序期间
  2. 所有字符串字面值都拥有'static生命周期,因为字符串字面值被直接存储在程序的二进制文件中而这个文件总是可用的
  3. 当编译器给出使用'static生命周期建议时,往往意味着出现了悬垂引用或生命周期不匹配的问题,而不是某一个引用真的有'static生命周期
let s: &'static str = "I have a static lifetime.";

结合泛型、trait和生命周期

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

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值