写点什么

每日一 R「09」类型系统(三)

作者:Samson
  • 2022 年 8 月 17 日
    上海
  • 本文字数:2893 字

    阅读完需:约 9 分钟

每日一R「09」类型系统(三)

合理地定义和使用 trait,会让代码结构具有很好的扩展性,让系统变得非常灵活。今天我们将学习几个常用的 trait。

  • 内存相关:Clone / Copy / Drop

  • 标记 trait:Sized / Send / Sync

  • 类型转换相关:From / Into/AsRef / AsMut

  • 操作符相关:Deref / DerefMut

01-内存相关:Clone / Copy / Drop

01.1-Clone trait

Clone trait 是 Rust 标准库中的一个 trait,标准库中对它的描述为:


A common trait for the ability to explicitly duplicate an object.


它声明了两个关联函数:clone 和 clone_from


pub trait Clone {    fn clone(&self) -> Self;    // 有缺省实现,所以为某个类型实现 Clone trait 时只需实现 clone 方法    fn clone_from(&mut self, source: &Self) { ... }}
复制代码


为某个类型实现 Clone trait 常用方式有如下两种:


  1. #[derive(Clone)]

  2. impl Clone for SomeStruct { … }

01.2-Copy trait

Copy trait 也是标准库中的 trait,它”继承”了 Clone trait。标准库中对它的描述为:


Types whose values can be duplicated simply by copying bits.


// 没有额外定义方法pub trait Copy: Clone { }
复制代码


像 Copy 这种没有任何行为的 trait,也被称作为 Marker(标记)trait,可以用作 trait bound 来进行类型安全检查。


默认情况下,变量绑定时具有 move 语义,即所有权会发生转移。如果变量的类型实现了 Copy trait,变量绑定时便具有 copy 语义,即所有权不发生转移,但值按位复制一份(浅拷贝)。考虑如下示例,示例 1 使用 move 语义,示例 2 使用 copy 语义:


这两个例子中,唯一的区别就是变量绑定let y = x;后,还允不允许继续访问 x。从本质上讲,复制或所有权转移都会发生内存按位拷贝,只不过 Rust 通过优化省略了所有权转移时的按位拷贝。


为某个类型实现 Copy trait 通常也有两种方式:


  1. #[derive(Copy, Clone)]

  2. impl Copy for SomeStruct { … } imple Clone for SomeStruct { … }

01.3-Clone vs. Copy

Clone trait 与 Copy trait 相比,不同点在于:


  • Copy 是隐式的(let x = y)、较低代价(an inexpensive bit-wise copy)的拷贝方式;

  • Clone 是显示的(x.clone()x.clone_from(&y))、代价可大可小(与具体情况相关);

  • Rust 不允许对 Copy 进行重新实现(not overloadable),允许对 Clone 重新实现;

  • Copy “继承“了 Clone,所以实现 Copy trait 的类型也必须实现 Clone trait;

01.4-Drop trait

Drop trait 是标准库中定义的一个 trait。标准库对它的描述如下:


Custom code within the destructor.


Drop trait 中只定义了一个方法 drop:


pub trait Drop {    fn drop(&mut self);}
复制代码


drop 方法会在值析构时被调用。学习所有权时我们了解到,当一个变量离开其作用域后,它拥有的值会被释放。这里的释放就是通过 Drop trait 中的 drop 方法实现的。其实一个值的析构器包含两部分内容:


  1. 调用值的 Drop::drop 方法

  2. 如果值是一个组合类型的值,则递归地调用每个值的析构器


大多数情况下不需要实现 Drop trait,除非有特殊的需求,例如:


  • 希望在数据声明周期结束时做额外的事情,例如打印日志。

  • 需要释放自管资源的场景,编译器并不知道程序额外使用了哪些资源,也就无法自动释放它们。


Copy and Drop are exclusive.


无法为某个类型同时实现 Drop trait 和 Copy trait。原因是,编译器会隐式为实现 Copy trait 的类型的值进行拷贝,使得编译器很难预测每个值何时会被释放。

02-标记 trait:Sized / Send / Sync

学习 Copy trait 的时候提到,它也被称为标记 trait,即没有声明任何关联方法。除了 Copy trait 外,Rust 中还有其他的几个常用的标记 trait,例如 Sized / Send / Sync / Unpin。

02.1-Sized trait

Types with a constant size known at compile time.


实现了 Sized trait 的类型拥有编译时可知的固定长度。在使用泛型参数是,Rust 会默认为泛型参数加上 : Sized类型约束。例如,如下两种写法是等效的:


struct Data<T>;struct Data<T: Sized>;
复制代码


如果需要 T 为可变长度的类型,可以通过?Sized进行类型约束。

02.2-Send / Sync trait

Send: Types that can be transferred across thread boundaries.Sync: Types for which it is safe to share references between threads.


pub unsafe auto trait Send { }pub unsafe auto trait Sync { }
复制代码


定义中的 auto 意味着编译器会在合适的场合自动为数据结构添加他们的实现。


This trait is automatically implemented when the compiler determines it’s appropriate.


定义中的 unsafe 意味着,如果开发着要自己实现这两个 trait,则需要为它们的安全性负责。


Send / Sync trait 是在多线程并发环境下使用的 trait,如果一个类型实现了 Send trait,则意味着它能够安全地从一个线程移动到另一个线程中,即所有权可以安全地在线程之间移动。如果一个类型实现了 Sync trait,则意味着引用可以在多个线程之间共享。


Send trait 与 Sync trait 的关系:如果一个类型 T 满足 Sync trait 当且仅当 &T 满足 Send trait。

03-类型转换相关:From / Into/AsRef / AsMut

03.1-From<T> / Into<T>

From<T> / Into<T> 是标准库中一对用于做 value-to-value 转换的 trait。它们的定义如下:


pub trait From<T> {    fn from(T) -> Self;}pub trait Into<T> {    fn into(self) -> T;}
复制代码


得益于标准库中的实现,如果一个类型实现了 From<T>,则会自动实现 Into<T>。这是由于:


// 实现 From 会自动实现 Intoimpl<T, U> Into<U> for T where U: From<T> {    fn into(self) -> U {        U::from(self)    }}
复制代码


如果 from 或 into 方法执行过程中可能出现错误,可以使用 TryFrom<T> / TryInto<T>,它们与 From<T> / Into<T> 一样,只不过返回结果是 Result<T, Self::Error>。从返回值上可以看出,TryFrom<T> / TryInto<T> 中包含了一个关联类型 Self::Error。

03.2-AsRef<T> / AsMut<T>

AsRef<T>: Used to do a cheap reference-to-reference conversion.AsMut<T>: Used to do a cheap mutable-to-mutable reference conversion.

04-操作符相关:Deref / DerefMut

Deref: Used for immutable dereferencing operations, like *v.DerefMut: Used for mutable dereferencing operations, like in *v = 1;


pub trait Deref {    // 解引用出来的结果类型    type Target: ?Sized;    fn deref(&self) -> &Self::Target;}
pub trait DerefMut: Deref { fn deref_mut(&mut self) -> &mut Self::Target;}
复制代码


对于实现了 Deref trait 的结构 T 来说,*(t:T) 时会自动调用 deref 方法,相当于 *(t.deref())。Rust 中绝大多数智能指针都实现了 Deref trait,例如之前学到的 Rc<T>:


impl<T: ?Sized> Deref for Rc<T> {    type Target = T;
fn deref(&self) -> &T { &self.inner().value }}
复制代码


本节课程链接:《14|类型系统:有哪些必须掌握的trait?


历史文章推荐

每日一 R「08」类型系统(二)

每日一 R「07」类型系统(一)

每日一 R「06」内存管理

每日一 R「05」生命周期

每日一 R「04」常用的智能指针

每日一 R「03」Borrow 语义与引用

每日一 R「02」所有权与 Move 语义

每日一 R「01」跟着大佬学 Rust

发布于: 刚刚阅读数: 3
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

还未添加个人简介

评论

发布
暂无评论
每日一R「09」类型系统(三)_8月月更_Samson_InfoQ写作社区