一文快速理解Rust语言扩展trait

2024-05-08 15:29:04 浏览数 (2)

科学无非就是在自然界的多样性中寻求统一性(或者更确切地说,是在我们经验的多样性中寻求统一性)。用 Coleridge 的话说,诗歌、绘画、艺术,同样是在多样性中寻求统一性

——Jacob Bronowski

Rust “实用工具” trait,这是标准库中各种 trait 的“百宝箱”,它们对 Rust 的编写方式有相当大的影响,所以,只有熟悉它们,你才能写出符合 Rust 语言惯例的代码并据此为你的 crate 设计公共接口,让用户认为这些接口是符合 Rust 风格的

语言扩展trait

运算符重载trait能让你在自己的类型上使用 Rust 的表达式运算符,同样,还有其他几个标准库 trait 也是 Rust 的扩展点,允许你把自己的类型更紧密地集成进语言中。这类trait包括 DropDerefDerefMut,以及转换trait FromInto

语言扩展trait汇总表

代码语言:javascript复制
析构器。每当丢弃一个值时,Rust 都要自动运行的清理代码

Drop

Drop 是标准库内置的,也是一个特殊的 trait,它定义了一个叫做 drop 的方法。这个方法在值离开作用域时被自动调用。这个特性可以用来执行一些清理工作,比如释放资源

代码语言:javascript复制
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中,DerefDerefMut 是两个 trait,它们允许我们重载解引用运算符 **mut

1. Deref trait:它定义了一个叫做 deref 的方法,这个方法返回一个引用。当我们对一个实现了Deref trait的类型使用 * 运算符时,deref 方法会被自动调用,返回一个引用,如下例子

代码语言:javascript复制
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

2. DerefMut trait:它定义了一个叫做 deref_mut 的方法,这个方法返回一个可变的引用。当我们对一个实现了 DerefMut trait 的类型使用 *mut 运算符时,deref_mut 方法会被自动调用,返回一个可变的引用

代码语言:javascript复制
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 用于从一种类型借入另一种类型的引用,而 FromInto 会获取其参数的所有权,对其进行转换,然后将转换结果的所有权返回给调用者

FromInto 的定义是对称的:

代码语言:javascript复制
trait Into<T>: Sized {
    fn into(self) -> T;
}

trait From<T>: Sized {
    fn from(other: T) -> Self;
}

From 特质

From 特质用于定义一个类型如何从另一个类型转换而来。实现了 From 特质后,可以使用 from 函数来进行类型转换。这个特质通常用于定义清晰的转换逻辑,定义了一个结构体 Number,并实现了从 i32Number 的转换

代码语言:javascript复制
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。如,使用 frominto 方法来进行类型转换

代码语言:javascript复制
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 的错误处理中尤其常见,例如将各种错误类型转换为统一的错误类型,使得错误处理更加统一和方便

注意!

FromInto 是不会失败的trait——它们的 API 要求这种转换不会失败。许多转换远比这复杂得多。例如,像 i64 这样的大整数可以存储比 i32 大得多的数值,如果没有一些额外的信息,那么将像 2_000_000_000_000i64 这样的数值转换成 i32 就没有多大意义。如果进行简单的按位转换,那么其中前 32 位就会被丢弃,通常不会产生我们预期的结果

代码语言:javascript复制
let huge = 2_000_000_000_000i64;
let smaller = huge as i32;
println!("{}", smaller); // -1454759936

有很多选项可以处理这种情况。根据上下文的不同,“回绕型”转换可能比较合适。另外,像数字信号处理和控制系统这样的应用程序通常会使用“饱和型”转换,它会把比可能的最大值还要大的数值限制为最大值

TryFrom 与 TryInto

由于转换的行为方式不够清晰,因此 Rust 没有为 i32 实现 From<i64>,也没有实现任何其他可能丢失信息的数值类型之间的转换,而是为 i32 实现了 TryFrom<i64>TryFromTryIntoFromInto 的容错版“表亲”,这种转换同样是双向的,实现了 TryFrom 也就意味着实现了 TryInto

TryFromTryInto 的定义比 FromInto 稍微复杂一点儿

代码语言:javascript复制
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 值转换,则会返回错误

代码语言:javascript复制
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,并处理可能的错误。在这个例子中,我们展示了如何使用 TryFromTryInto 来处理可能失败的转换

代码语言:javascript复制
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),
    }
}

使用场景

  • 当输入数据的有效性不确定时,使用 TryFromTryInto 可以安全地尝试进行类型转换
  • 它们常用于处理外部数据,如用户输入、文件读取等,这些数据可能不满足我们的预期格式或范围

这两个特质提供了一种类型安全的方式来处理可能错误的转换,使得代码更加健壮和易于维护

FromInto 可以将类型与简单转换关联起来,而 TryFromTryInto 通过 Result 提供的富有表现力的错误处理扩展了 FromInto 的简单转换。这 4 个trait可以一起使用,在同一个 crate 中关联多个类型

小结

语言扩展 trait 已经了解了,里面有很多新的概念,虽然敲了示例代码,距离熟练掌握还有很长的路要走,还需多敲代码,在实践中夯实基础

欢迎大家讨论交流,如果喜欢本文章或感觉文章有用,动动你那发财的小手点赞、收藏、关注再走呗 ^_^

0 人点赞