泛型定义#
我一直把泛型理解为类型系统中的参数,因为在 TS 中,类型和执行代码存在隔离。泛型定义就好像一个类型中的函数,通过传入参数得到具体的类型。同时,能通过泛型约束参数,返回值或结构体字段之间的关系.
fn get_value<T>(v: T) -> T {
v
}
enum Result<T, U> {
Ok(T),
Err(U)
}
struct List<T> {
items: Vec<T>,
current: T
}
impl<T> List<T> {
fn first(&self) -> T {
&self.items[0]
}
}
为结构体定义方法时也可以限定具体的类型实现类似重载的操作
impl List<i32> {
fn sum(&self) -> i32 {
let mut result = 0;
for item in &self.items {
result += item
}
result
}
}
// 指定 trait
impl<T: Display> List<T> {
fn sum(&self) -> i32 {}
}
Rust 的泛型代码不会影响性能,编译时会对其进行单态化, 也就是把使用到泛型的代码编译为具体类型的代码,有点像 Vue3 的静态优化.
Trait#
这玩意有点绕啊
Trait 中文特征,跟 TS 对标类似 interface
或者 abstract class
. 我理解为用来对泛型进行约定和限制,让某些抽象的泛型具象了一些,还可以拓展类型添加行为.
trait Desc {
fn read(&self) -> &str;
fn default() -> &str {
"default impl"
}
}
impl<T> Desc for List<T> {
fn read(&self) -> &str {
"a list"
}
}
实现 Trait 受相干性限制,实现方或者被实现方至少有一个在本地作用域才能进行,这条规则可以避免其他人在 crate 以外破坏代码.
抽象之上的抽象实现 blank implementations.
// 为所有实现过 Display 的类型实现 ToString
impl<T: Display> ToString for T {}
定义后就可以进行约束了
fn get_desc(target: &impl Desc) {}
// trait bound
fn print_desc<T: Desc + Display>(target: &T) -> impl Display {}
fn print_default_desc<T>(target: &T) where T: Desc + Display {}
生命周期#
生命周期的存在是为了保证引用有效,避免悬垂引用。因为所有权的限制,被引用的对象不能比引用者存在时间短,我们不可能在函数体内创建一个引用然后返回,否则离开作用域时源数据被销毁,返回的引用将成为悬垂引用。所以如果一个函数返回了引用,则引用一定与参数中的引用有关联。这个关联有时候无法通过代码推断,比如传入两个引用返回了一个引用,这时就需要我们去明确指定生命周期辅助编译器完成推断.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// 根据逻辑不需要为 y 指定生命周期
fn first<'a>(x: &'a str, y: &str) -> &'a str {
x
}
struct Person<'a> {
name: &'a str
}
// 静态生命周期, 有点像 any, 使用前要保证其能存在整个程序运行过程
let s:&'static str = "hello"
省略规则#
Rust 内置了一些可预测的模式减少重复代码,如果在这些规则之后仍存在无法推断的生命周期,就需要我们明确指定.
- 每个引用的生命周期必须与至少一个输入引用的生命周期相同。
- 如果只有一个输入引用,那么输出引用的生命周期与输入引用的生命周期相同。
- 如果有多个输入引用,其中一个是
&self
或&mut self
,那么输出引用的生命周期与该输入引用的生命周期相同。 - 如果有多个输入引用,其中一个是
&self
,那么输出引用的生命周期与另一个输入引用的生命周期相同。