托尼托尼·乔巴 和 凯撒·库朗
科学无非就是在自然界的多样性中寻求统一性(或者更确切地说,是在我们经验的多样性中寻求统一性)。用 Coleridge 的话说,诗歌、绘画、艺术,同样是在多样性中寻求统一性
——Jacob Bronowski
Rust “实用工具” trait,这是标准库中各种 trait 的“百宝箱”,它们对 Rust 的编写方式有相当大的影响,所以,只有熟悉它们,你才能写出符合 Rust 语言惯例的代码并据此为你的 crate 设计公共接口,让用户认为这些接口是符合 Rust 风格的
语言扩展trait
运算符重载trait能让你在自己的类型上使用 Rust 的表达式运算符,同样,还有其他几个标准库 trait 也是 Rust 的扩展点,允许你把自己的类型更紧密地集成进语言中。这类trait包括 Drop
、Deref
和 DerefMut
,以及转换trait From
和 Into
语言扩展trait汇总表
trait | 描述 |
---|---|
Drop |
析构器。每当丢弃一个值时,Rust 都要自动运行的清理代码 |
Deref 与 DerefMut
|
智能指针类型的trait |
From 与 Into
|
用于将一种类型的值转换为另一种类型的转换trait |
TryFrom 与 TryInto
|
用于将一种类型的值转换为另一种类型的转换trait,用于可能失败的转换 |
Drop
Drop 是标准库内置的,也是一个特殊的 trait,它定义了一个叫做 drop
的方法。这个方法在值离开作用域时被自动调用。这个特性可以用来执行一些清理工作,比如释放资源
struct MyType;
impl Drop for MyType {
fn drop(&mut self) {
println!("Dropping MyType");
}
}
fn main() {
let my_type = MyType;
// my_type 离开作用域,drop方法被自动调用
}
在这个例子中,我们定义了一个 MyType 类型,并为它实现了 Drop trait。当 my_instance 离开作用域时,drop
方法会被自动调用,打印出 "Dropping MyType"
注意!
Rust的Drop trait是在值离开作用域时被自动调用的,而不是在值被销毁时。这意味着,如果一个值被移动到另一个作用域,它的drop方法不会被调用
当一个值的拥有者消失时,Rust 会丢弃(drop)该值。丢弃一个值就必须释放该值拥有的任何其他值、堆存储和系统资源。丢弃可能发生在多种情况下:当变量超出作用域时;在表达式语句的末尾;当截断一个向量时,会从其末尾移除元素;等等
Deref 与 DerefMut
通过实现 std::ops::Deref
trait 和 std::ops::DerefMut
trait ,可以指定像 *
和 .
这样的解引用运算符在你的类型上的行为
在Rust中,Deref
和 DerefMut
是两个 trait,它们允许我们重载解引用运算符 *
和 *mut
-
Deref
trait:它定义了一个叫做deref
的方法,这个方法返回一个引用。当我们对一个实现了Deref
trait的类型使用*
运算符时,deref
方法会被自动调用,返回一个引用,如下例子
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
-
DerefMut
trait:它定义了一个叫做deref_mut
的方法,这个方法返回一个可变的引用。当我们对一个实现了DerefMut
trait 的类型使用*mut
运算符时,deref_mut
方法会被自动调用,返回一个可变的引用
use std::ops::DerefMut;
impl<T> DerefMut for MyBox<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
From 与 Into
std::convert::From
trait 和 std::convert::Into
trait 表示类型转换,这种转换会接受一种类型的值并返回另一种类型的值。AsRef
trait 和 AsMut
trait 用于从一种类型借入另一种类型的引用,而 From
和 Into
会获取其参数的所有权,对其进行转换,然后将转换结果的所有权返回给调用者
From
和 Into
的定义是对称的:
trait Into<T>: Sized {
fn into(self) -> T;
}
trait From<T>: Sized {
fn from(other: T) -> Self;
}
From 特质
From
特质用于定义一个类型如何从另一个类型转换而来。实现了 From
特质后,可以使用 from
函数来进行类型转换。这个特质通常用于定义清晰的转换逻辑,定义了一个结构体 Number
,并实现了从 i32
到 Number
的转换
use std::convert::From;
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
Into 特质
Into
特质是 From
的互补特质。如果为类型 A
实现了 From<B>
,那么同时也自动为 B
实现了 Into<A>
。这意味着你可以使用 into
方法将类型 B
转换为类型 A
。如,使用 from
和 into
方法来进行类型转换
fn main() {
let my_number = Number::from(30);
let int: i32 = 5;
let num: Number = int.into();
println!("My number is {}", my_number.value);
println!("Num is {}", num.value);
}
使用场景
- 当你需要一个明确的转换方法时,使用
From
- 当你需要为多种类型提供灵活的转换方式时,使用
Into
这两个特质在 Rust 的错误处理中尤其常见,例如将各种错误类型转换为统一的错误类型,使得错误处理更加统一和方便
注意!
From
和 Into
是不会失败的trait——它们的 API 要求这种转换不会失败。许多转换远比这复杂得多。例如,像 i64
这样的大整数可以存储比 i32
大得多的数值,如果没有一些额外的信息,那么将像 2_000_000_000_000i64
这样的数值转换成 i32
就没有多大意义。如果进行简单的按位转换,那么其中前 32 位就会被丢弃,通常不会产生我们预期的结果
let huge = 2_000_000_000_000i64;
let smaller = huge as i32;
println!("{}", smaller); // -1454759936
有很多选项可以处理这种情况。根据上下文的不同,“回绕型”转换可能比较合适。另外,像数字信号处理和控制系统这样的应用程序通常会使用“饱和型”转换,它会把比可能的最大值还要大的数值限制为最大值
TryFrom 与 TryInto
由于转换的行为方式不够清晰,因此 Rust 没有为 i32
实现 From<i64>
,也没有实现任何其他可能丢失信息的数值类型之间的转换,而是为 i32
实现了 TryFrom<i64>
。TryFrom
和 TryInto
是 From
和 Into
的容错版“表亲”,这种转换同样是双向的,实现了 TryFrom
也就意味着实现了 TryInto
TryFrom
和 TryInto
的定义比 From
和 Into
稍微复杂一点儿
pub trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
pub trait TryInto<T>: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
TryFrom 特质
TryFrom
特质用于定义一个可能失败的类型转换。如果转换可能因为某些原因失败(例如,超出范围、格式错误等),则使用 TryFrom
。它返回一个 Result 类型,成功时包含目标类型,失败时包含错误信息。如这个例子中,SmallNumber
只接受 0 到 255 范围内的 i32
值。如果尝试从超出此范围的 i32
值转换,则会返回错误
use std::convert::TryFrom;
struct SmallNumber {
value: u8,
}
impl TryFrom<i32> for SmallNumber {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value >= 0 && value <= 255 {
Ok(SmallNumber { value: value as u8 })
} else {
Err("Number out of range".to_string())
}
}
}
TryInto 特质
TryInto
特质是 TryFrom
的互补特质。如果为类型 A
实现了 TryFrom<B>
,那么同时也自动为 B
实现了 TryInto<A>
。这意味着你可以使用 try_into
方法尝试将类型 B
转换为类型 A
,并处理可能的错误。
在这个例子中,我们展示了如何使用 TryFrom
和 TryInto
来处理可能失败的转换
fn main() {
let large_number: i32 = 1000;
let small_number: Result<SmallNumber, _> = SmallNumber::try_from(large_number);
match small_number {
Ok(n) => println!("Small number is {}", n.value),
Err(e) => println!("Error: {}", e),
}
let another_number: i32 = 150;
let result: Result<SmallNumber, _> = another_number.try_into();
match result {
Ok(n) => println!("Small number is {}", n.value),
Err(e) => println!("Error: {}", e),
}
}
使用场景
- 当输入数据的有效性不确定时,使用
TryFrom
和TryInto
可以安全地尝试进行类型转换 - 它们常用于处理外部数据,如用户输入、文件读取等,这些数据可能不满足我们的预期格式或范围
这两个特质提供了一种类型安全的方式来处理可能错误的转换,使得代码更加健壮和易于维护
From
和 Into
可以将类型与简单转换关联起来,而 TryFrom
和 TryInto
通过 Result
提供的富有表现力的错误处理扩展了 From
和 Into
的简单转换。这 4 个trait可以一起使用,在同一个 crate 中关联多个类型
小结
语言扩展 trait 已经了解了,里面有很多新的概念,虽然敲了示例代码,距离熟练掌握还有很长的路要走,还需多敲代码,在实践中夯实基础
欢迎大家讨论交流,如果喜欢本文章或感觉文章有用,动动你那发财的小手点赞、收藏、关注再走呗
^_^