Bootstrap

泛型、Trait和生命周期(10)

1.泛型数据类型

1.1在函数定义中使用泛型

  • 两个函数,不同点只是名称和签名类型
fn largest_i32(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}


fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest_i32(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest_char(&char_list);
    println!("The largest char is {}", result);
}

  • 泛型初试

/*
    error
         fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {}
    
    泛型T没实现PartialOrd,标准库 i32,char 都实现了PartialOrd
    所以,泛型T: PartialOrd + Copy,可以解决
    
    error: the trait bound `T: std::cmp::PartialOrd` is not satisfied


*/
// fn largest<T>(list: &[T]) -> &T {
//     let mut largest = &list[0];

//     for item in list {
//         if item > largest {
//             largest = item;
//         }
//     }

//     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 result = largest(&number_list);
    println!("The largest number is {}", result);
    let result = largest(&char_list); 
    println!("The largest char is {}", result);
}

1.2结构体定义中的泛型

struct Point<T> {
    x: T,
    y: T,
}
// 通过T U泛型,可以指定x y为不同的数据类型
struct Points<T, U> {
    x: T,
    y: U,
}

fn main(){
    let p1 = Point{x: 1, y: 2};
    println!("({},{})", p1.x, p1.y);

    let p2 = Points{x: 1.0, y: 52};
    println!("({},{})", p2.x, p2.y);
}

1.3枚举定义中的泛型

enum Option<T> {
	Some(T),
	None,
}

enum Result<T, E> {
	Ok(T),
	Err(E),
}

1.4方法定义中的泛型

  • 方法使用了与结构体定义中不同类型的泛型
struct Point<T, U>{
    x: T,
    y: U,
}
impl<T, U> Point<T, U>{
    fn x(&self) -> &T{
        &self.x
    }
    fn y(&self) -> &U{
        &self.y
    }
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W>{
        Point{
            x: self.x,
            y: other.y,
        }
    }
}


fn main(){
    let p1 = Point{x: 5, y: 10.4};
    let p2 = Point{x: "Hello", y: 'c'};
    let p3 = p1.mixup(p2);
    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);// 5 c
}

1.5泛型代码的性能

  • Rust 通过在编译时进行泛型代码的 单态化(monomorphization)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程

2.Trait: 定义共同行为

  • trait 类似于其他语言中的常被称为 接口(interfaces)的功能

2.1定义trait

  • 一个类型的行为由其可供调用的方法构成
  • 如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为
  • trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合
pub trait Summary {
    fn summarize(&self) -> String;
}

2.2为类型实现trait

  • 在NewArticle和Tweet类型上实现 Summary 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 aggregate::{Summary, 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());
}

2.3默认实现

  • trait中的某些或全部方法提供默认的行为,而不是在每个类型的每个实现中都定义自己的行为是很有用的
// lib.rs
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

2.4trait作为参数

pub fn notify(item: &impl Summary){
	println!("Breaking news! {}", item.summarize());
}

2.5Trait Bound语法

// Trait Bound语法
pub fn notify<T: Summary>(item: &T) {
	println!("Breaking news! {}", item.summarize());

}

// 非Trait Bound语法
pub fn notify(item: &impl Summary, item2: &impl Summary){}

2.6通过 + 指定多个trait bound

pub fn notify (item: &(impl Summary + Display)){}

// + 语法也适用于泛型的trait bound

pub fn notify<T: Summary + Display> (item: &T){}

2.7通过where 简化 trait bound

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T,u: &U) -> i32{}

// 通过where简化
fn some_function<T,U>(t: &T,u: &U) -> i32
where
	T: Display + Clone,
	U: Clone + Debug,
{}

2.8返回实现了trait的类型

  • 可以通过返回值中使用 impl trait 语法,来返回实现了某个trait的类型
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

3.生命周期确保引用有效

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

  • 生命周期的主要目标是避免悬垂引用,后者会导致程序引用了非预期引用的数据
fn main() {
    let r;

    {
        let x = 5;
        r = &x; // error,   borrowed value does not live long enough

    }// 此处,x就释放内存

    
    println!("r: {}", r);
}

3.2借用检查器

  • rust编译器有一个借用检查器,比较作用域来确保所有的借用都是有效的
fn main() {
    // let r;

    // {
    //     let x = 5;
    //     r = &x;

    // }


    // println!("r: {}", r);

    let y = 5;
    let e = &y;

    println!("e: {}", e);//  e: 5
}

3.3函数中的泛型生命周期

// 借用检查器自身同样也无法确定,因为它不知道 x 和 y 的生命周期
fn main() {  
    let string1 = String::from("abcd");  
    let string2 = "xyz";  

    let result = longest(&string1, string2);   
    println!("The longest string is {}", result);  
}
fn longest(a: &str, b: &str) -> &str {  
    if a.len() > b.len() {  
        a  
    } else {  
        b  
    }  
}  

3.4生命周期注解语法

  • 生命周期注解并不改变任何引用的生命周期的长短
  • 生命周期参数名称必须以撇号( ’ )开头,其名称通常全是小写,类似于泛型其名称非常短
  • 大多数人使用 'a 作为第一个生命周期注解
&i32			//引用
&'a i32			//带有显式生命周期的引用
&'a mut i32		//带有显式生命周期的可变引用

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

  • 为了在函数签名中使用什么周期注解,需要在函数名和参数列表间的尖括号中声明泛型生命周期参数
  • 当在函数中使用生命周期注解时,这些注解出现在函数签名中,而不存在于函数体中的任何代码中
  • 通过在函数签名中指定生命周期参数时,我们并没有改变任何传入值或返回值的生命周期,而是指出任何不满足这个约束条件的值都将被借用检查器拒绝
fn main() {  
    let string1 = String::from("abcd");  
    let string2 = "xyz";  

    let result = longest(&string1, string2);   
    println!("The longest string is {}", result);  
}
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {  
    if a.len() > b.len() {  
        a  
    } else {  
        b  
    }  
}  
注意事项

fn main() {  
    // string2 在作用域结束前有效
    let string1 = String::from("abcd");  
    {
        let string2 = "xyz";  

        let result = longest(&string1, string2);   
        println!("The longest string is {}", result); 
    }

    // let string1 = String::from("abcd"); 
    // let result; 
    // {
    //     let string2 = "xyz";  

    //     result = longest(&string1, string2);   
        
    // }
    // // string2 需要知道外部作用域结束都是有效的,longest函数返回的是一个引用,所以result需要知道string2的作用域结束
    // // 否则编译器会报错
    // println!("The longest string is {}", result); 


}
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {  
    if a.len() > b.len() {  
        a  
    } else {  
        b  
    }  
}  

3.6深入理解生命周期

  • 指定生命周期参数的正确方式依赖函数实现的具体功能
  • 返回的引用 没有 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值
  • 它将会是一个悬垂引用,因为它将会在函数结束时离开作用域
fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

3.7结构体定义中的生命周期注解

  • 定义的结构体全都包含拥有所有权的类型
  • 定义包含引用的结构体,不过这需要为结构体定义中的每一个引用添加生命周期注解

// 定义包含引用的结构体,需要为结构体定义中的每一个引用添加生命周期注解
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt { part: first_sentence };
    println!("{}", i.part);
}

3.8生命周期省略

  • 函数或方法的参数的生命周期被称为 输入生命周期
  • 返回值的生命周期被称为 输出生命周期
1.采用三条规则来判断引用何时不需要明确的注解
  • 第一条规则是编译器为每一个引用参数都分配一个生命周期参数
  • 第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32
  • 第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self,说明是个对象的方法 (method),那么所有输出生命周期参数被赋予 self 的生命周期
  • 编译器检查完这条规则后仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误

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

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

3.10静态生命周期

  • 'static ,其生命周期能够存活于整个程序期间

3.11结合泛型类型参数、trait bounds和生命周期

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