2022-02-11_星期五

读库2200

桥梁书”是帮助读者去接近,理解核心价值的书。比如牛顿的书可能谁都看不懂或者看不下去,但我们可以看一本屋里科普书,能理解就行了。

这里我推荐一本科普书:从一到无穷大

绘本确实适合小孩,也适合成年人。它不仅仅是让成年人重温自己烂漫无邪的少年时代、童年时光,好的绘本一定是直通人性、普照人生的,不管是几岁小朋友的心灵,还是几十岁大人的精神世界。

阅读不只是让人掌握知识而已,而是要通过掌握这些知识,激发自己的创造力、想象力,包括知识迁移能力,如果没有对知识的这种迁移、嫁接、拿捏、整合的能力,学富五车也没什么用。

读书和学习时两件事。读书并不是在学习,不能把读书这件事定义为学习。

陈天 · Rust 编程第一课

比如数据结构实现了 Debug trait,那么当你想打印数据结构时,就可以用 {:?} 来打印;如果你的数据结构实现了 From<T>,那么,可以直接使用 into() 方法做数据转换。

常见 trait:

  • Clone / Copy trait,约定了数据被深拷贝和浅拷贝的行为;
  • Read / Write trait,约定了对 I/O 读写的行为;
  • Iterator,约定了迭代器的行为;
  • Debug,约定了数据如何被以 debug 的方式显示出来的行为;
  • Default,约定数据类型的缺省值如何产生的行为;
  • From / TryFrom,约定了数据间如何转换的行为。

Clone trait 有两个方法, clone() 和 clone_from() ,后者有缺省实现,所以平时我们只需要实现 clone() 方法即可。

Clone trait

clone()clone_from() 区别:如果 a 已经存在,在 clone 过程中会分配内存,那么用 a.clone_from(&b) 可以避免内存分配,提高效率。

如果在你的数据结构里,每一个字段都已经实现了 Clone trait,你可以用 #[derive(Clone)]

Clone 是深度拷贝,栈内存和堆内存一起拷贝。

Copy trait

Copy trait 没有任何额外的方法,它只是一个标记 trait(marker trait)。

1
pub trait Copy: Clone {}

如果要实现 Copy trait 的话,必须实现 Clone trait,然后实现一个空的 Copy trait。

这样的 trait 虽然没有任何行为,但它可以用作 trait bound 来进行类型安全检查,所以我们管它叫标记 trait

  • (Rust 还支持其它几种标记 trait:Sized / Send / Sync / Unpin)

如果数据结构的所有字段都实现了 Copy,也可以用 #[derive(Copy)] 宏来为数据结构实现 Copy。

如果类型实现了 Copy,那么在赋值、函数调用的时候,值会被拷贝,否则所有权会被移动。

Drop trait

大部分场景无需为数据结构提供 Drop trait,系统默认会依次对数据结构的每个域做 drop。但有两种情况你可能需要手工实现 Drop:

  1. 希望在数据结束生命周期的时候做一些事情,比如记日志。
  2. 需要对资源回收的场景。编译器并不知道你额外使用了哪些资源,也就无法帮助你 drop 它们。

Copy trait 和 Drop trait 是互斥的,两者不能共存,当你尝试为同一种数据类型实现 Copy 时,也实现 Drop,编译器就会报错。

Sized trait

在使用泛型参数时,Rust 编译器会自动为泛型参数加上 Sized 约束。

如果开发者显式定义了 T: ?Sized,那么 T 就可以是任意大小。

1
2
3
4
5
6
7
pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned,
{
    // 借用的数据
    Borrowed(&'a B),
    // 拥有的数据
    Owned(<B as ToOwned>::Owned),
}

B 可以是 [T] 或者 str 类型,大小都是不固定的。要注意 Borrowed(&'a B) 大小是固定的,因为它内部是对 B 的一个引用,而引用的大小是固定的。

Send / Sync

Send/Sync 是 Rust 并发安全的基础:

  • 如果一个类型 T 实现了 Send trait,意味着 T 可以安全地从一个线程移动到另一个线程,也就是说所有权可以在线程间移动
  • 如果一个类型 T 实现了 Sync trait,则意味着 &T 可以安全地在多个线程中共享
    • 一个类型 T 满足 Sync trait,当且仅当 &T 满足 Send trait。

在线程安全中的作用:

  • 如果一个类型 T: Send,那么 T 在某个线程中的独占访问是线程安全的;
  • 如果一个类型 T: Sync,那么 T 在线程间的只读共享是安全的。

对于我们自己定义的数据结构,如果其内部的所有域都实现了 Send / Sync,那么这个数据结构会被自动添加 Send / Sync 。

标准库中,不支持 Send / Sync 的数据结构主要有:

  • 裸指针 *const T / *mut T
    • 它们是不安全的,所以既不是 Send 也不是 Sync。
  • UnsafeCell<T> 不支持 Sync。
    • 也就是说,任何使用了 Cell 或者 RefCell 的数据结构不支持 Sync。
  • 引用计数 Rc 不支持 Send 也不支持 Sync。
    • 所以 Rc 无法跨线程。

在多线程情况下,我们只能使用支持 Send/Sync 的 Arc ,和 Mutex 一起,构造一个可以在多线程间共享且可以修改的类型。

From / Into

对值类型的转换和对引用类型的转换,Rust 提供了两套不同的 trait:

  • 值类型到值类型的转换:From<T> / Into<T> / TryFrom<T> / TryInto<T>
  • 引用类型到引用类型的转换:AsRef<T> / AsMut<T>

在实现 From<T> 的时候会自动实现 Into<T>

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

需要的时候,不要去实现 Into<T>,只要实现 From<T> 就好了。

如果你的数据类型在转换过程中有可能出现错误,可以使用 TryFrom<T>TryInto<T>,它们的用法和 From<T> / Into<T> 一样,只是 trait 内多了一个关联类型 Error,且返回的结果是 Result<T, Self :: Error>

操作符相关

Rust 为所有的运算符都提供了 trait,你可以为自己的类型重载某些操作符。

20220211124144.png

Debug / Display

Debug 和 Display 两个 trait 的签名一样,都接受一个 &self 和一个 &mut Formatter。

  • Debug 是为开发者调试打印数据结构所设计的,而 Display 是给用户显示数据结构所设计的。
  • Debug trait 的实现可以通过派生宏直接生成,而 Display 必须手工实现。在使用的时候,Debug 用 {:?} 来打印,Display 用 {} 打印。

Default trait

Default trait 用于为类型提供缺省值。它也可以通过 derive 宏 #[derive(Default)] 来生成实现,前提是类型中的每个字段都实现了 Default trait。在初始化一个数据结构时,我们可以部分初始化,然后剩余的部分使用 Default::default()

20220211124519.png

不同角度看延迟绑定:

  • 数据结构是具体数据的延迟绑定
  • 泛型结构是具体数据结构的延迟绑定
  • 函数是一组实现某个功能的表达式的延迟绑定
  • 泛型函数函数的延迟绑定
  • trait行为的延迟绑定
    • 我们可以在不知道具体要处理什么数据结构的前提下,先通过 trait 把系统的很多行为约定好。
updatedupdated2022-03-092022-03-09