本文是对Programming Rust 第13章的总结,部分代码与图片来自此书
Utility Traits
rust中有很多常用的trait用来表达一些基础的语言特性, 这些特性贯穿了整个rust语言的设计与应用,熟悉这些trait对于编写rust代码是必须的
Drop
drop发生在一个所有者终结的时候,他所持有的值就要一起被销毁, 这些值包括堆内存,系统资源等等。drop通常发生在作用域结束,表达式结束或者Vec移除对象的时候。大部分时候rust会自动帮你销毁对象,这正是Borrow-Check机制的特别之处,在没有GC的情况下做自动内存管理
Drop被定义如下
| |
rust会在值销毁的时候自动调用drop, 另外手工调用drop是不被允许的, rust编译器会报错
以下例子表明了drop发生的时机
| |
在这个例子中drop发生了两次, 一次是重新赋值的时候,另外一次是作用域结束的时候
大多数情况,我们并不需要自己实现Drop, 但是有些时候rust编译器识别不出值所拥有的资源类型的时候, 你需要手工来释放资源,比如标准库中的文件句柄,通过drop来主动释放对于文件句柄的占用
| |
一个实现了Drop的类型,不能同时实现Copy,因为Copy代表了类型可以通过字节复制来完成拷贝, 这种情况下可能会对于同样的数据drop多次, 这种做法是错误的。
Sized
Sized代表了某种类型拥有固定大小, rust中大部分类型都是Sized, 数字,字符串,数组等。
Sized是一种Marker Trait, 这表示它并不需要代码实现,而是由编译器自动实现
以下三种类型是Unszied Type
- str
- slice
- trait object(dyn trait)
以下是三种类型的内存示意图

rust并不能直接把Unsized Type放在变量中使用, 通常是通过放在引用或者智能指针后面使用比如&str, &[T],Box::<dyn Writer>.
除了以上三种类型之外, struct也允许在最后一个字段放置一个Unsized Type使它自己也变成Unszie Type,比如标准库中Rc的私有类型RcBox
| |
对于一个Unsize Type你并不能直接构建, 而是需要通过引用的方式间接实现
另外在泛型边界中, 由于约束Sized是很普遍的情况, 所以默认所有泛型<T>都会被自动转换成T:Sized, 如果你需要Unsized Type那么可以显示的指定为T:?Sized
Clone
Clone就是对一个值的深复制, 标准库中的大部分类型都是适合复制的,也就是实现了Clone,但是某些特定的类型不合适,比如std::sync::Mutex是不允许复制的, 因为锁是不能复制的,另外std::fs::File也是可以复制的, 但是由于涉及到操作系统调用, 并不能保证复制成功, 所以它也没有实现Clone相反它提供了一个try_clone方法
Copy
Copy也是一种Marker Trait,它表明了一个类型可以进行字节浅复制,在所有Move发生的情况下, 如果一个类型是Copy类型,那么Move不会发生, 取而代之的是复制,也就是原来的值持有者保持可用
Copy只可以标记那些能够进行浅复制的类型, 这个约束又编译器来保证, 另外实现了Drop的类型不能同时实现Copy,两者不能同时存在于一个类型中
Deref and DerefMut
Deref是用来表达解引用,解引用与引用是一对相反的操作, 对于引用类型来说可以用*T来表示, 为了把这种操作推广到自定义类型,所以有了这一组Trait . 但是事实上能用到*的地方其实很少,考虑下面的类型
| |
这是一个可以解引用为String的类型, 也就是说Deref<MyString>->String
那么看一下我们可以怎么使用这个特性
| |
从上面的例子可以看出,Deref只是参与了*操作, 两者并不等价, *v操作可能等于*(v.deref())也可能等于v.deref().copy(), 具体怎么desugar还要看使用情况。我们需要在直觉方面保持一致
需要指出的是Box<T>是一个比较特别的类型, 它可以通过*Box<T>将T给Move出来
| |
AsRef和AsMut
这两个trait允许从实现了T:AsRef<U>的类型T中获取&U, 也是一种满足灵活性的设计, 比如使用AsRef<U>作为函数参数, 就允许传入更多的类型
需要说明的是, 泛型并不会做强制类型转换, 比如
| |
str类型实现了AsRef, 但是通常情况下, 我们是使用&str来进行编码, 这种情况编译器将在类型检查的时候无法通过, 为了解决这个问题, 标准库包含了一个通用实现
| |
这使得, 任何T: AsRef<U>都同时有 &T:AsRef<U>, 也就是通过T,&T都可以得到&U类型
Borrow和BorrowMut
Borrow与AsRef类似,也是获取T: AsBorrow<U>,也是通过T获取&U的方式.所不同的是, AsBorrow通常约定T与U之间有某种联系,比如HashMap<K,V>
| |
HashMap<K, V>中存储的是K,V类型, 但是它允许使用Q满足K: Borrow<Q>来查询K对应的值, 这必然需要提供一下约束来保证实现, 因为HashMap与hash, eq,ord等运算符有关系, 比如要保证hash(K1)==hash(K2)的时候hash(Q1)==hash(Q2).
当你能满足K与Q的Eq,Ord, Hash都一致的时候就实现AsBorrow,否则就实现AsRef .另外Borrow也有一些通用实现,保证了K, &K, &mut K 作为K的时候, &K作为Q,也就是实现了K: AsBorrow<K>, &K: AsBorrow<K>, &mut K: AsBorrow<K>,当然还有更多的智能指针比如Box<K>: AsBorrow<K>
From和Into
这是一组一样用途的Trait,实现了From就会自动实现Into . T: From<U>表示可以从U转换成T类型。From的一个典型用途就是?操作符, 它可以将错误进行转换, 符合函数需要的错误类型,从而简化代码
| |
ToOwned
ToOwned的定义如下
| |
它允许从&T中生成一个U: Borrow<T>, 这使得它的使用范围比Clone要大, 你可以从多种可能的类型中选择一种, Clone只能返回T类型,比如[T]可以实现成ToOwned<Owned=Vec<T>>, 因为Vec<T>:Borrow<[T]>, 相反[T]因为是Unsized Type, 是不允许实现Clone的
Cow
Cow是Copy On Write的意思,它的定义是一个Enum类型
| |
可以看出来,它利用了Borrow和ToOwned两种Trait来实现, 一个Cow即可以是一个&B, 也是可以是T: Borrow<B>, 其中T是值, &B是引用, 当必要的时候这两个之前可以互相转换
| |