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

托尼托尼·乔巴 和 凯撒·库朗

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

——Jacob Bronowski

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

语言扩展trait

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

语言扩展trait汇总表

trait 描述
Drop 析构器。每当丢弃一个值时,Rust 都要自动运行的清理代码
DerefDerefMut 智能指针类型的trait
FromInto 用于将一种类型的值转换为另一种类型的转换trait
TryFromTryInto 用于将一种类型的值转换为另一种类型的转换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中,DerefDerefMut 是两个 trait,它们允许我们重载解引用运算符 **mut

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

FromInto 的定义是对称的:

trait Into<T>: Sized {
    fn into(self) -> T;
}

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

From 特质

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

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 方法来进行类型转换

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 位就会被丢弃,通常不会产生我们预期的结果

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 稍微复杂一点儿

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

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 已经了解了,里面有很多新的概念,虽然敲了示例代码,距离熟练掌握还有很长的路要走,还需多敲代码,在实践中夯实基础

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容