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

283 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第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 / Into

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

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

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

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

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

03.2-AsRef / AsMut

AsRef: Used to do a cheap reference-to-reference conversion. AsMut: 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:

impl<T: ?Sized> Deref for Rc<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.inner().value
    }
}

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


历史文章推荐