这大概是我学过的最恶心的一节了
提取函数 消除重复代码
重复代码
- 重复代码的危害
- 容易出错
- 需求变更时需要在多处进行修改
- 消除重复:提取函数
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 告诉 Rust 编译器
-
定义的一个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的约束
- 可以在某个类型上实现某个trait的前提条件是:
- 这个类型或这个tarit是在本地crate里定义的
- 无法为外部类型来实现外部的trait
- 这个限制是程序属性的一部分(也就是一致性)
- 更具体地说是孤儿规则:之所以这样命名是因为父类型不存在
- 此规则确保其他人的代码不能破坏您的代码,反之亦然
- 如果没有这个规则,两个crate可以为同一类型实现同一个trait,Rust就不知道应该使用哪个实现了。
- 可以在某个类型上实现某个trait的前提条件是:
-
默认实现
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引用分析中所编入的模式称为生命周期省略规则。
- 这些规则无需开发者来遵循
- 它们是一些特殊情况,由编译器来考虑
- 如果你的代码符合这些情况,那么就无需显式标注生命周期
- 生命周期省略规则不会提供完整的推断
- 如果应用规则后,引用的生命周期仍然模糊不清 -> 编译错误
- 解决办法:添加生命周期标注,表明引用间的相互关系
- 在Rust引用分析中所编入的模式称为生命周期省略规则。
-
输入、输出生命周期
- 生命周期在:
- 函数/方法的参数:输入生命周期
- 函数/方法的返回值:输出生命周期
- 生命周期在:
-
生命周期省略的三个规则
- 编译器使用3个规则在没有显式标生命周期的情况下,来确定引用的生命周期
- 规则1应用于输入生命周期
- 规则2、3应用于输出生命周期
- 如果编译器应用完3个规则之后,仍然有无法确定生命周期的引用-》报错
- 这些规则适用于fn定义和Impl块
- 规则1: 每个引用类型的参数都有自己的生命周期
- 规则2:如果只有1个输入生命周期参数,那么该生命mfka德维尔被赋给所有的输出生命周期参数
- 规则3:如果有多个输入生命周期参数, 但其中一个是&self或 &mut self(是方法),那么self的生命周期会被赋给所有的输出生命周期参数
- 编译器使用3个规则在没有显式标生命周期的情况下,来确定引用的生命周期
-
静态生命周期
-
‘static 是一个特殊的生命周期:整个程序的持续时间
- 例如: 所有的字符串字面值都拥有‘static生命周期
- let s: &'static str = 'i have a static lifetime';
- 例如: 所有的字符串字面值都拥有‘static生命周期
-
为引用指定‘static'生命周期前要三思:
- 是否需要引用在程序整个生命周期内都存活
-
泛型参数类型、trait bound、生命周期
-