本文是对Programming Rust 第13章的总结,部分代码与图片来自此书

Utility Traits

rust中有很多常用的trait用来表达一些基础的语言特性, 这些特性贯穿了整个rust语言的设计与应用,熟悉这些trait对于编写rust代码是必须的

Drop

drop发生在一个所有者终结的时候,他所持有的值就要一起被销毁, 这些值包括堆内存,系统资源等等。drop通常发生在作用域结束,表达式结束或者Vec移除对象的时候。大部分时候rust会自动帮你销毁对象,这正是Borrow-Check机制的特别之处,在没有GC的情况下做自动内存管理

Drop被定义如下

1
2
3
trait Drop {
 fn drop(&mut self);
}

rust会在值销毁的时候自动调用drop, 另外手工调用drop是不被允许的, rust编译器会报错

以下例子表明了drop发生的时机

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 struct Appellation {
     name: String,
     nicknames: Vec<String>,
 }

 impl Drop for Appellation {
     fn drop(&mut self) {
         print!("Dropping {}", self.name);
         if !self.nicknames.is_empty() {
             print!(" (AKA {})", self.nicknames.join(", "));
         }
         println!("");
     }
 }

 {
     let mut a = Appellation {
         name: "Zeus".to_string(),
         nicknames: vec![
             "cloud collector".to_string(),
             "king of the gods".to_string(),
         ],
     };
     println!("before assignment");
     //a Owner another value, so the old value drop
     a = Appellation {
         name: "Hera".to_string(),
         nicknames: vec![],
     };
     println!("at end of block");
     //a drop the value it owns
 }

在这个例子中drop发生了两次, 一次是重新赋值的时候,另外一次是作用域结束的时候

大多数情况,我们并不需要自己实现Drop, 但是有些时候rust编译器识别不出值所拥有的资源类型的时候, 你需要手工来释放资源,比如标准库中的文件句柄,通过drop来主动释放对于文件句柄的占用

1
2
3
4
5
6
7
8
9
struct FileDesc {
 fd: c_int,
}

impl Drop for FileDesc {
 fn drop(&mut self) {
 let _ = unsafe { libc::close(self.fd) };
 }
}

一个实现了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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct RcBox<T: ?Sized> {
 ref_count: usize,
 value: T,
}

let boxed_lunch: RcBox<String> = RcBox {
 ref_count: 1,
 value: "lunch".to_string()
};
use std::fmt::Display;
let boxed_displayable: &RcBox<dyn Display> = &boxed_lunch;

对于一个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 . 但是事实上能用到*的地方其实很少,考虑下面的类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct MyString {
        str: String,
    }

impl Deref for MyString {
    type Target = Stri
    fn deref(&self) -> &Self::Target {
        &self.str
    }

impl DerefMut for MyString {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.str
    }
}

这是一个可以解引用为String的类型, 也就是说Deref<MyString>->String

那么看一下我们可以怎么使用这个特性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 let mut  my_string = MyString{str: "abc".to_string()};
 //无法通过编译, 因为不能Move &类型,
 let string = *my_string;
 let string = *(my_string.deref());
//通过强制接引用(deref coercion)实现,无需显示写出,也很难写,因为这里面不仅涉及到解引用,还涉及到借引用, 反正是神奇的dot operator的黑魔法
  _ = my_string.len();
//必须实现DerefMut才可以
 *my_string = "def".to_string();
 *(my_string.deref_mut()) = "def".to_string();
//跟下面的是一个意思
let mutstr = &mut "123".to_string();
*mutstr = "def".to_string();

从上面的例子可以看出,Deref只是参与了*操作, 两者并不等价, *v操作可能等于*(v.deref())也可能等于v.deref().copy(), 具体怎么desugar还要看使用情况。我们需要在直觉方面保持一致

需要指出的是Box<T>是一个比较特别的类型, 它可以通过*Box<T>将T给Move出来

1
2
3
let mut a = Box::new("abc".to_string());
//可以编译通过, 应该是编译器特性
let b = *a;

详细情况可以参考这个RFC

AsRef和AsMut

这两个trait允许从实现了T:AsRef<U>的类型T中获取&U, 也是一种满足灵活性的设计, 比如使用AsRef<U>作为函数参数, 就允许传入更多的类型 需要说明的是, 泛型并不会做强制类型转换, 比如

1
impl AsRef<[u8]> for str {}

str类型实现了AsRef, 但是通常情况下, 我们是使用&str来进行编码, 这种情况编译器将在类型检查的时候无法通过, 为了解决这个问题, 标准库包含了一个通用实现

1
2
3
4
5
6
7
8
impl<'a, T, U> AsRef<U> for &'a T
 where T: AsRef<U>,
 T: ?Sized, U: ?Sized
{
 fn as_ref(&self) -> &U {
 (*self).as_ref()
 }
}

这使得, 任何T: AsRef<U>都同时有 &T:AsRef<U>, 也就是通过T,&T都可以得到&U类型

Borrow和BorrowMut

Borrow与AsRef类似,也是获取T: AsBorrow<U>,也是通过T获取&U的方式.所不同的是, AsBorrow通常约定T与U之间有某种联系,比如HashMap<K,V>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::borrow::Borrow;
use std:#️⃣:Hash;

pub struct HashMap<K, V> {
    // fields omitted
}

impl<K, V> HashMap<K, V> {
    pub fn insert(&self, key: K, value: V) -> Option<V>
    where K: Hash + Eq
    {
        // ...
    }

    pub fn get<Q>(&self, k: &Q) -> Option<&V>
    where
        K: Borrow<Q>,
        Q: Hash + Eq + ?Sized
    {
        // ...
    }
}

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的一个典型用途就是?操作符, 它可以将错误进行转换, 符合函数需要的错误类型,从而简化代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//所有实现了std::err::Error的类型都可以转换成Box<dyn Error>
impl<'a, E: Error + Send + Sync + 'a> From<E>
 for Box<dyn Error + Send + Sync + 'a> {
 fn from(err: E) -> Box<dyn Error + Send + Sync + 'a> {
 Box::new(err)
 }
}

type GenericError = Box<dyn std::error::Error + Send + Sync +
'static>;
type GenericResult<T> = Result<T, GenericError>;
fn parse_i32_bytes(b: &[u8]) -> GenericResult<i32> {
 Ok(std::str::from_utf8(b)?.parse::<i32>()?)
}

ToOwned

ToOwned的定义如下

1
2
3
4
trait ToOwned {
 type Owned: Borrow<Self>;
 fn to_owned(&self) -> Self::Owned;
}

它允许从&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类型

1
2
3
4
5
6
enum Cow<'a, B: ?Sized>
 where B: ToOwned
{
 Borrowed(&'a B),
 Owned(<B as ToOwned>::Owned),
}

可以看出来,它利用了Borrow和ToOwned两种Trait来实现, 一个Cow即可以是一个&B, 也是可以是T: Borrow<B>, 其中T是值, &B是引用, 当必要的时候这两个之前可以互相转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::path::PathBuf;
use std::borrow::Cow;
fn describe(error: &Error) -> Cow<'static, str> {
 match *error {
 Error::OutOfMemory => "out of memory".into(),
 Error::StackOverflow => "stack overflow".into(),
 Error::MachineOnFire => "machine on fire".into(),
 Error::Unfathomable => "machine bewildered".into(),
 Error::FileNotFound(ref path) => {
 format!("file not found: {}", path.display()).into()
 }
 }
}