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

泛型、Trait和生命周期

提取函数来减少重复

1.在一个数字列表中寻找最大值的函数

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

2.寻找两个数字列表最大值的代码

  • 重复的代码是冗余且容易出错的,更新逻辑时我们不得不记住需要修改多处地方的代码
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);



    let number_list = vec![102,34,6000,89,54,2,43,8];
    let mut largest = &number_list[0];
    for number in &number_list{
        if number > largest{
            largest = number;
        }
    }
    println!("The largest number is {}",largest);
}

3.抽象后的寻找两个数字列表最大值的代码

  • largest函数有一个参数list,它代表会传递给函数的任何具体的 i32 值的 slice
  • 函数定义中的list代码任何 &[i32]
  • 代码优化
    • 找出重复代码
    • 重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入返回值
    • 重复代码的两个实例,改为调用函数
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: &[i32]) -> &i32{
    let mut largest = &list[0];
    for item in list{
        if item > largest{
            largest = item;
        }
    }
    largest
}

1.泛型数据类型

1.1在函数定义中使用泛型

  • 使用泛型定义函数时,本来在函数签名中指定参数返回值的类型的地方,会改用泛型来表示
  • 采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复
1.两个函数,不同点只是名称和签名类型
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);


    let char_list = vec!['y','m','a','q'];
    let result = largest_char(&char_list);
    println!("The largest char is {}",result);
}
fn largest(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{
        if item > largest{
            largest = item;
        }
    }
    largest
}
2.一个使用泛型参数的largest函数定义,尚不能编译
//  添加代码,修改   
// use std::cmp::PartialOrd;
// fn largest<T :PartialOrd>(list: &[T]) -> &T{}

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<T>(list: &[T]) -> &T{
    let mut largest = &list[0];
    for item in list{
   	 	// binary operation '>' cannot be applied to type '&T'
        // if item > largest{
            // largest = item;
        // }
    }
    largest
}

1.2结构定义中的泛型

  • 用<>语法来定义结构体,它包含一个或多个泛型参数类型字段
1.Point结构体存放了两个T类型的值x 和 y
  • 语法类似于函数定义中使用泛型
  • 必须在结构体名称后面的尖括号中声明泛型参数的名称
  • 在结构体定义中可以指定具体数据类型的位置使用泛型类型
fn main() {
    let integer = Point {x: 5,y: 10};
    let float = Point{x:1.0,y: 4.0};
}
struct Point<T>{
    x: T,
    y: T,
}
2.字段x和y的类型必须相同,因为它们都有相同的泛型类型T
  • 编译器Point< T>实例中的泛型T全是整型
fn main() {
    // error expected integer,found floating-point number
    // let wont_work = Point{x:5,y:6.0};
}
struct Point<T>{
    x: T,
    y: T,
}
3.使用两个泛型的Point,这样x和y可能是不同类型
fn main() {
    let both_integer = Point{x:5,y:10};
    let both_float = Point{x:1.0,y:4.0};
    let integer_and_float = Point{x:5,y:4.0};
}
struct Point<T,U>{
    x: T,
    y: U,
}

1.3枚举定义中的泛型

  • 和结构体类型,枚举也可以在成员中存放泛型数据类型
enum Option<T> {
	Some(T),
	None,
}
  • 枚举可以拥有多个泛型类型
enum Result<T,E>{
	Ok(T),
	Err(E),
}

1.4方法定义中的泛型

1.在Point结构体上实现方法x,它返回T类型的字段x的引用
  • 注意必须在 impl 后面声明 T,这样就可以在Point< T>上实现的方法中使用T
  • 通过在impl之后声明泛型TRust就知道Point的尖括号中的类型是泛型而不是具体类型
  • impl 中编写的方法声明了泛型类型可以定位为任何类型的实例,不管最终替换泛型类型的是何具体类型

fn main() {
    let p = Point{x:5,y:10};
    println!("p.x = {}",p.x())
}
struct Point<T>{
    x: T,
    y: T,
}
impl<T> Point<T>{
    fn x(&self) -> &T{
        &self.x
    }
}

2.构建一个只用于拥有泛型参数T的结构体的具体类型的impl块
  • 定义方法时也可以为泛型指定限制
impl Point<f32>{
	fn distance_from_origin(&self) -> f32{
		(self.x.powi(2) + self.y.powi(2)).sqrt()
	}
}
3.方法使用了与结构体定义中不同类型的泛型

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

1.5泛型代码的性能

  • Rust通过在编译时进行泛型代码的单态化来保证效率
  • 单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定的过程
  • 泛型 Option< T> 被编译器替换为了具体的定义
  • Rust 会将每种情况下的泛型代码编译为具体类型,使用泛型没有运行时开销

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}
enum Option_i32{
    Some(i32),
    None,
}
enum Option_f64 {
    Some(f64),
    None,
}

2.Trait: 定义共同行为

  • trait类似于其他语言中的常被称为接口的功能,虽然有一些不同

2.1定义trait

  • 一个类型的行为由其可供调用的方法构成
  • 可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为
  • trait定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合
1.Summary trait定义,它包含由summarize方法提供的行为
  • trait体中可以有多个方法,一行一个方法签名且都以分号结尾
pub trait Summary{
    fn summarize(&self)->String;
}

2.2为类型实现trait

1.在NewsArticle和Tweet类型上实现Summary 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)
    }
}

2.3默认实现

  • trait中的某些或全部方法提供默认的行为,而不是在每个类型的每个实现中定义自己的行为是很有用的
  • 某个特定类型实现trait,可以选择保留重载每个方法的默认行为
1.Summary trait的定义,带有一个summarize方法的默认实现
pub trait Summary{
    // 默认实现   
    fn summarize(&self)->String{
        String::from("(Read more....)")
    }
}
2.impl Summary for NewsArticle {}指定一个空的impl块
let article = 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.",
        ),
    };
    println!{"New article available! {}",article.summarize()}; // New article available! (Read more....)



impl Summary for NewsArticle{
    
}
3.默认实现允许调用相同trait中的其他方法,哪怕这些方法没有默认实现
  • 无法从相同方法的重载实现中调用默认方法
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());
}
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)
    }
}

2.4trait作为参数

  • 对于item参数,指定了impl关键字和trait名称,而不是具体的类型
  • 参数支持任何实现了指定trait的类型
  • 任何用其它如Stringi32的类型调用该函数的代码都不能编译,因为他们没有实现Summary
pub trait Summary{
    fn summarize(&self)->String;
}
pub fn notify(item: &impl Summary){
    println!("Breaking news! {}",item.summarize());
}

1.Trait Bound语法
  • impl Trait 语法适用于直观的例子,实际上是一种较长形式语法的语法糖
  • impl Trait很方便,适用于短小的例子
  • 更长的trait bound则适用于更复杂的场景
// pub fn notify(item: &impl Summary){
//     println!("Breaking news! {}",item.summarize());
// }
pub fn notify<T: Summary>(item: &T){
    println!("Breaking news! {}",item.summarize());
}

pub fn notify(item1: &impl Summary,item2: &impl Summary){}
// item1 和 item2允许是不同类型的情况
pub fn notify<T: Summary>(item1: &T,item2: &T){}
2.通过 + 指定多个trait bound
  • notify需要显示 item 的格式化形式,也要使用summarize方法
pub fn notify(item: &(impl Summary + Display)){}
  • +语法也适用于泛型的trait bound
pub fn notify<T: Summary + Display>(item: &T){}
  • 指定这两个trait boundnotify的函数体可以调用summarize并使用{ }来格式化 item
3.通过where简化trait bound
  • Rust有另个在函数签名之后的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.5返回实现了trait的类型

  • 返回值使用 impl Trait 语法,来返回实现某个 trait 的类型
fn return_summarizeable() -> impl Summary{
    Tweet{
        username: String::from("horse_ebooks"),
        content: String::from("of course,as you probably already know,people"),
        reply: false,
        retweet: false,
    }
}
1.闭包和迭代器创建只有编译器知道的类型,或者是非常非常长的类型
  • impl Trait允许你简单的指定函数返回一个Iterator,而无需写出实际的冗长的类型
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 int the NHL",
            ),
        }
    } else{
        Tweet{
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course,as you probably already know,people",
            ),
            reply: false,
            retweet: false,
        }
    }
}

2.6使用trait bound有条件地实现方法

  • 通过使用带有 trait bound 的泛型参数的impl块,可以有条件地只为那些实现了特定trait的类型实现方法
1.根据trait bound在泛型上有条件的实现方法
  • impl块中,只有那些为T类型实现了PartialOrd trait(来允许比较) 和 Display trait(来启用打印)的Pair< T>才会实现cmp_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,被广泛的用于Rust标准库
impl<T: Display> ToString for T { }

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

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

  • 生命周期的主要目标是避免悬垂引用,导致程序引用了非预期引用的数据
1.尝试使用离开作用域的值的引用
  • 在rust中,没有初始化值的变量,这些变量存在于外部作用域
  • Rust不允许存在空值相冲突
fn main() {
    // 声明外部作用域
    // let r;

    {
        let x = 5;
        // borrowed value does not live long enough
        // r = &x;
    }
    // println!("r: {}",r);
}
1.借用检查器
  • Rust编译器有一个借用检查器,比较作用域来确保所有的借用都是有效的
1.r和x的生命周期注解,分别叫做 'a 和 'b
  • r 的生命周期标记为 'a 并将 x 的生命周期标记为 'b
  • 编译时,Rust比较这个两个生命周期的大小,并发现 r 拥有生命周期 'a,不过它引用了一个拥有生命周期 'b的对象
  • 程序被拒绝编译,因为生命周期 'b 比生命周期 'a要小: 被引用的对象比它的引用者存在的时间更短
fn main() {
	let r;						//--------------+---- 'a
	{							//				|
		let x = 5;				//---+---- 'b	|
		r = &x;					//	 |			|
	}							//---+			|
	println!("r: {}",r);		//				|
}								//--------------+
2.一个有效的引用,因为数据比引用有着更长的生命周期
  • x拥有生命周期 'b,比 'a要大
fn main() {
	let x = 5;				//-------------------+-------'b
							//					 |
	let r = &x;				//----+---'a		 | 
							//	  |				 |
	println!("r: {}",r);	//	  |				 |
							//----+				 |
}							//-------------------+

3.2函数中的泛型生命周期

1.main函数调用longest函数来寻找两个字符串 slice 中较长的一个
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(),string2);
    println!("The longest string is {}",result);
}
2.一个longest函数的实现,它返回两个字符串 slice 中较长者,现在还不能编译
  • 返回值需要一个泛型生命周期函数,在Rust并不知道将要返回的引用是指向x 或 y
  • 函数体中if 块返回一个 x 的引用而else 块返回一个y的引用
// this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
fn longest (x: &str,y: &str) ->&str{
    if x.len() > y.len(){
        x
    }else{
        y
    }
}

3.3生命周期注解语法

  • 生命周期注解并不改变任何引用的生命周期的长短
  • 多个引用生命周期相互的关系,而不影响其生命周期
  • 函数签名中指定了泛型类型参数后就可以接受任何类型一样,当指定了泛型生命周期后函数也能接受任何生命周期的引用
  • 生命周期参数名称必须以撇号( ’ )开头,其名称通常全是小写,类似于泛型其名称非常短
&i32			//引用
&'a i32			//带有显式生命周期的引用
&'a mut i32		//带有显式生命周期的可变引用

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

  • 为了在函数签名中使用生命周期注解,需要在函数名和参数列表间的尖括号中声明泛型生命周期参数,就像泛型类型参数一样
  • 两个参数返回的引用存活的一样久
  • (两个)参数返回的引用的生命周期是相关的
1.longest函数定义指定了签名中所有的引用必须有相同的生命周期
  • 函数签名表明对于某些生命周期’a,函数会获取两个参数,都是与生命周期 'a 存在的一样长的字符串slice
  • 函数会返回一个同样与生命周期’a存在的一样长的字符串slice
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}",result);// The longest string is abcd
}
fn longest<'a>(x: &'a str,y: &'a str) -> &'a str{
    if x.len() > y.len(){
        x
    }else{
        y
    }
}
2.通过拥有不同的具体生命周期的String值调用longest函数
  • string1直到外部作用域结束都是有效的,string2则在内部作用域中是有效的
fn main() {
    let string1 = String::from("long string is long");
    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(),string2.as_str());
        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
    }
}

3.尝试在string2离开作用域之后使用result
fn main() {
    let string1 = String::from("long string is long");
    // let result;
    {
        let string2 = String::from("xyz");
        // `string2` does not live long enough
        // borrowed value does not live long enough
        // result = longest(string1.as_str(),string2.as_str());
        
    }
    // 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
    }
}

3.5深入理解生命周期

  • 指定生命周期参数的正确方法依赖函数实现的具体功能
fn longest<'a>(x: &'a str,y: &str) -> &'a str{
	x
}
  • 函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配
fn longest<'a>(x: &str,y: &str) -> &str{
	let result = String::from("really long string");
	// returns a reference to data owned by the current function
	// result.as_str()
}

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

  • 定义的结构体全都包含拥有所有权的类型,也可以定义包含引用的结构体
  • 需要为结构体定义中的每一个引用添加生命周期注解
1.一个存放引用的结构体,所以其定义需要生命周期注解
  • 结构体有唯一个字段part,存放了一个字符串slice,一个引用
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,
	};
}

3.7生命周期省略(Lifetime Elision)

  • 每一个引用都有一个生命周期,需要为那些使用了引用的函数结构体指定生命周期
1.定义了一个没有使用生命周期注解的函数,即便其参数和返回值都是引用
fn first_word(s: &str) -> &str{
	let bytes = s.as_bytes();
	for(i,item) in bytes.iter().enumerate(){
		if item == b' '{
			return &s[0..i];	
		}
	}
	&s[..]
}
2.被编码进Rust引用分析的模式被称为生命周期省略规则
  • 规则是一系列特定场景,此时编译器会考虑,如果代码符合这些场景,就无需明确指定生命周期
  • 函数方法的参数的生命周期被称为 输入生命周期 ,而返回值的生命周期被称为 输出生命周期
fn first_word<'a>(s: &'a str) -> &'a str{ }

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

  • 第一条规则是编译器为每一个引用参数都分配一个生命周期参数

  • 第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数

  • 第三天规则是如果方法有多个输入生命周期参数并且其中一个参数是 &self&mut self,说明是个对象的方法(method),那么所有输出生命周期参数被赋予 self 的生命周期

  • impl块里的方法签名中,引用可能与结构体字段中的引用相关联,也可能是独立的

struct  ImportantExcerpt<'a> {
    part: &'a str,
}
impl<'a> ImportantExcerpt<'a>{
    fn level(&self)->i32{
        3
    }
}
1.第三条生命周期省略规则
impl<'a> ImportantExcerpt<'a>{
	fn announce_and_return_part(&self,announcement: &str) -> &str{
		println!("Attention please: {}",announcement);
		self.part
	}
}

3.9 静态生命周期

  • 'static,其生命周期能够存活于整个程序期间
  • 所有的字符串字面值拥有**'static** 生命周期
let s: &'static str = "I have a static lifetime.";

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

  • ann的类型是泛型T,可以被放入任何实现了where从句中指定Display trait的类型
  • 生命周期也是泛型,生命周期参数’a泛型类型参数T都位于函数后后的同一尖括号列表中
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
	}
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>