| Trait | Description |
|---|---|
| Drop | Destructors. Cleanup code that Rust runs automatically whenever a value is dropped. |
| Sized | Marker trait for types with a fixed size known at compile time, as opposed to types (such as slices) that are dynamically sized. |
| Clone | Types that support cloning values. |
| Copy | Marker trait for types that can be cloned simply by making a byte-for-byte copy of the memory containing the value. |
| DerefandDerefMut | Traits for smart pointer types. |
| Default | Types that have a sensible “default value.” |
| AsRefandAsMut | Conversion traits for borrowing one type of reference from another. |
| BorrowandBorrowMut | Conversion traits, like AsRef/AsMut, but additionally guaranteeing consistent hashing, ordering, and equality. |
| FromandInto | Conversion traits for transforming one type of value into another. |
| TryFromandTryInto | Conversion traits for transforming one type of value into another, for transformations that might fail. |
| ToOwned | Conversion trait for converting a reference to an owned value. |
Drop
trait Drop {fn drop(&mut self);}
drop都是隐式调用的,如果自己去掉会报错。如果实现了DropRust在drop值所包含的内存的时候会先执行实现的drop方法。
一般来说用户不用实现Drop,除非有一些Rust不知道怎么处理的资源需要释放。比如Rust的文件类型需要使用C的函数来释放
impl Drop for FileDesc {fn drop(&mut self) {let _ = unsafe { libc::close(self.fd) };}}
Rust的prelude里有个一个drop函数可以用来drop一个值:fn drop<T>(_x: T) { }。但这个函数其实就是获取值的所有权,然后什么都不做。
Sized
一个Sized类型就是这个类型的值所占的内存大小永远是一样的。几乎所有的类型都是Sized,即便是Vec,因为他是一个指针加长度加容量,可变的只是堆上的buffer。
所有大小不变的类型都实现了Sized,而且Sized没有关联的方法,Rust自动为所有适用的类型实现了Sized。程序员自己并不能实现Sized。这个trait的唯一作用就是用作类型变量的bound。这样的trait叫marker trait(记号特性?)。
大小不确定的类型:
切片,比如str和数组切片[T]。平时用的都是&str和&[T],这两个都是指向切片的指针,而切片本身的大小是不确定的。
dyn,也就是trait object指向的值。因为实现了某个trait的可能是任何类型,所以大小是不能确定的。
因为unsized值是不能保存到变量里的,所以只能通过指针来使用他们。
因为Sized类型占绝多数,所以在声明类型参数的时候不用指明T: Sized。而当要使用unsized的类型的时候才需要注明:T: ?Sized。
一个struct的最后一个字段可以是?Sized,而且只有这个字段可以。如果有这样的字段,那这个struct就是一个?Sized。Rc的内部实现就有一个这样的struct,RcBox。这样的结构体无法直接创建,必须先创建一个Sized版的,然后赋给unsized版的引用类型
struct Foo<T: ?Sized> {foo: T}fn main() {let foo_sized: Foo<[i8; 3]> = Foo { foo: [1,2 ,3] };let foo_unsized: &Foo<[i8]> = &foo_sized;for &i in foo_unsized.foo.iter() {println!("{}", i);}}
Clone
Clone表示类型可以复制。他的定义:
trait Clone: Sized {fn clone(&self) -> Self;fn clone_from(&mut self, source: &Self) {*self = source.clone()}}
clone一个值会复制这个值的所有字段和其字段包含的值,所以是特别耗费时间和内存的,但Rc和Arc除外,他们只是增加引用计数。
默认的clone_from会clone source然后赋给*self,但有的类型可能会优化,比如s = t.clone(),需要首先drop s的值然后申请内存复制一份t的值,然后把所有权给s。但如果用clone_from,而且s的capacity是足够容纳t的,那只需要将t的内容复制到s的buffer里然后修改len值就可以了,不用drop旧内存,申请新内存。
大多数标准库的类型是可以clone的,但有例外:std::sync::Mutex不能clone。std::fs::File有可能失败,所以std::fs::File有try_clone会返回std::io::Result<File>。
Copy
Copy是一个marker trait,是Clone的subtrait。对于编译器来说,Copy类型的值只需要一个shallow copy,所以拥有资源的类型是不能impl Copy的。
实现了Drop的类型都不能是Copy。对于Clone类型可以使用#[derive(Copy)]来实现Copy。
Deref和DerefMut
实现std::ops::Deref和std::ops::DerefMut让你可以自定义取值操作*和.的行为。在引用那章说过的自动引用:String可以使用&str的方法,Vec<T>可以直接调用&[T]的方法等,是因为String实现了Deref<Target=str>。Vec实现了Deref<Target=[T]>。这种行为叫deref coercions,一个方法的参数是某种类型T,如果deref操作能够将一个类型变成T,那Rust就会自动进行类型转换。
这两个trait的设计初衷是为了实现smart pointer,以及一些“拥有所有权的类型”但经常以引用的形式使用的类型,比如Vec和String。推荐不要把这两个trait用作自动获取目标类型的方法,像是C++那样子类自动获取父类方法的那样。一个应用的列子:
struct Selector<T> {/// Elements available in this `Selector`.elements: Vec<T>,/// The index of the "current" element in `elements`. A `Selector`/// behaves like a pointer to the current element.current: usize}use std::ops::{Deref, DerefMut};impl<T> Deref for Selector<T> {type Target = T;fn deref(&self) -> &T {&self.elements[self.current]}}impl<T> DerefMut for Selector<T> {fn deref_mut(&mut self) -> &mut T {&mut self.elements[self.current]}}let mut s = Selector { elements: vec!['x', 'y', 'z'],current: 2 };// Because `Selector` implements `Deref`, we can use the `*` operator to// refer to its current element.assert_eq!(*s, 'z');// Assert that 'z' is alphabetic, using a method of `char` directly on a// `Selector`, via deref coercion.assert!(s.is_alphabetic());// Change the 'z' to a 'w', by assigning to the `Selector`'s referent.*s = 'w';assert_eq!(s.elements, ['x', 'y', 'w']);
但在泛型中类型bound中,Rust不会应用deref coercions:
let s = Selector { elements: vec!["good", "bad", "ugly"],current: 2 };fn show_it(thing: &str) { println!("{}", thing); }show_it(&s);use std::fmt::Display;fn show_it_generic<T: Display>(thing: T) { println!("{}", thing); }show_it_generic(&s);
上面第二个函数调用show_it_generic(&s);会报错,因为Rust不在类型bound中应用deref coercions。但可以手动写明强制引用:
show_it_generic(&s as &str);// 或者show_it_generic(&*s);
Default
一些类型有明显的默认值,比如数字的默认值可以是0,字符串的默认值是空串。这样的类型可以实现Default,用来返回默认值:
trait Default {fn default() -> Self;}
常见用法:
Iterator的partition方法会按某个closure的输出来讲讲一个iterator的元素分到两个collection里。而目标collection的类型就需要实现Default,还需要实现Extend。partition的方法是先用default创建两个默认值,然后用Extend往创建的默认值里添加元素。
use std::collections::HashSet;let squares = [4, 9, 16, 25, 36, 49, 64];let (powers_of_two, impure): (HashSet<i32>, HashSet<i32>)= squares.iter().partition(|&n| n & (n-1) == 0);assert_eq!(powers_of_two.len(), 3);assert_eq!(impure.len(), 4);let (upper, lower): (String, String)= "Great Teacher Onizuka".chars().partition(|&c| c.is_uppercase());assert_eq!(upper, "GTO");assert_eq!(lower, "reat eacher nizuka");
如果一个类型T实现了Default,则rust会自动为T的一些smart pointer类型实现Default。
如果tuple的所有元素类型都有Default,则该tuple就有Default。
struct不会自动实现Default,但如果struct的所有字段类型都有Default,则可以使用#[derive(Default)]实现Default。
AsRef和AsMut
trait AsRef<T: ?Sized> {fn as_ref(&self) -> &T;}trait AsMut<T: ?Sized> {fn as_mut(&mut self) -> &mut T;}
这两个trait的功能是方便的从一个类型U中借用一个&T。比如打开文件的函数std::fs::File::open需要的参数是&Path,但传入String,&str等都可以,就是因为这些类型实现了AsRef
blanket implementation:用泛型的方式给某些类型统一实现一个trait。
impl<'a, T, U> AsRef<U> for &'a Twhere T: AsRef<U>,T: ?Sized, U: ?Sized{fn as_ref(&self) -> &U {(*self).as_ref()}}
实现了AsRef的不一定能应该实现AsMut,因为有些数据修改会破坏原数据的规则。比如String实现了AsRef<[u8]>。但却不能实现AsMut因为String是UTF-8编码的,如果变成&mut [u8]之后修改了某个字节,可能就不是一个正确的UTF-8编码了。
Borrow和BorrowMut
Borrow和AsRef基本相同,只是增加了一个限制:要求其实现返回的&T和原来的类型(这个类型不一定是T)能得到相同的hash和比较的结果。其定义和AsRef基本一样:
trait Borrow<Borrowed: ?Sized> {fn borrow(&self) -> &Borrowed;}
Borrow的设计初衷是为了解决哈希表和其他类似的类型的一些情况:
impl<K, V> HashMap<K, V> where K: Eq + Hash{fn get(&self, key: K) -> Option<&V> { ... }}
如果用key取value的方法定义是这样的,那在调用的时候还要创建一个K类型的值,然后转移到方法里用掉。如果吧K换成&K:
impl<K, V> HashMap<K, V> where K: Eq + Hash{fn get(&self, key: &K) -> Option<&V> { ... }}
那在调用的时候:hashtable.get(&”twenty-two”.to_string())。要把”twenty-two”转换成String(申请heap内存),然后取该String的引用,传递给get,执行完get之后就drop掉。这样更荒谬。所以真实的方法是这样的:
impl<K, V> HashMap<K, V> where K: Eq + Hash{fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>where K: Borrow<Q>,Q: Eq + Hash{ ... }}
只要传入的值能borrow一个&Q就行,这样如果方法需要的是&str,那传入的变量可以是String,可以是字面量,不用重新创建值或者申请内存。标准库用blanket implementation给所有的类型都实现了T: Borrow
From和Into
和AsRef类似,AsRef是从原值创建一个引用,而Into是将原值转移返回一个新值,From是获取原值的所有权得到一个新值。
trait Into<T>: Sized {fn into(self) -> T;}trait From<T>: Sized {fn from(other: T) -> Self;}
标准库自动给所有类型都实现了自己转换成自己的的From和Into。
From还起到了构建方法的作用。标准库自动的给实现了From的类型实现了对应的Into。所以如果自定义的类型有从一个参数构建实例的方法,最好用实现From的形式。
因为From和Into会获取所有权,所以生成的新值可能会使用原值的旧资源,这样可以提高性能。因为会获取所有权并转成其他类型,程序的一些类型限制会变得宽松,比如程序想把String当成普通的字节使用,把使用类型转换就让这样的操作变得可能。
这样的类型转换并不“便宜”,因为构建一个新的实例可能会申请内存,拷贝内存等。
?使用了From和Into,在需要的时候会把一个具体的错误类型变成一个更通用的类型。
TryFrom and TryInto
From和Into是不会出错的,Try*是可能出错的版本,会返回Result。
这样可以用Result的方法来处理意外或错误的情况,比如unwrap_or和unwrap_or_else。
ToOwned
trait ToOwned {type Owned: Borrow<Self>;fn to_owned(&self) -> Self::Owned;}
和Clone类似,Clone只能复制一个类型到一个相同的类型,ToOwened可以复制一个类型去构建另外一个类型,只要这个类型实现了Borrow。
Cow
前面的Borrow或者ToOwned,要么获取一个引用要么获取一个值,在编译的时候就确定了。std::borrow::Cow可以在运行时决定是要获取引用还是值(clone on write)。
例子
- 根据错误类型返回不同的错误信息,有些错误是提前知道的返回的可以是字符串常量 &’static str,如果一些没有准备好的字符串,可以在运行时生成一个字符串String。
- 从Vec生成字符串,如果给的vec是正确的utf8,则只需要将原vec转换类型变成&str。不需要申请内存。而如果有无效的utf-8。则需要插入�。这是时候因为改变了原字符串的长度,需要重新申请内存,返回String。 ```rust // some bytes, in a vector let sparkle_heart = vec![240, 159, 146, 150];
let sparkle_heart = String::from_utf8_lossy(&sparkle_heart);
assert_eq!(“💖”, sparkle_heart);
可以看出Cow有个好处就是可以在需要的时候才申请内存。```rustenum Cow<'a, B: ?Sized>where B: ToOwned{Borrowed(&'a B),Owned(<B as ToOwned>::Owned),}
为什么Owned的类型是<B as ToOwned>::Owned:
参考上面ToOwen的关联类型是type Owned: Borrow<Self>,也就是说Owned是一个可以借出&B的类型,所以如果B是[u8],那Borrowed就是&[u8],Owned就是Vec[u8]。所以如果想让Cow返回一个字符串引用或者有heap内存的String,需要把Cow定义成:Cow<’a, str>。
Cow实现了Deref,所以可以直接调用B的方法。
虽然Borrowed的类型是一个不可变的引用(分享引用,&’a B),但如果需要一个可变的引用,可以调用Cow的to_mut方法生成一个可变的引用,这时候Cow会用Borrowed里的引用复制一个值出来,把自己变成Owned,然后再返回一个可变的引用,这也就是clone on write。Cow也有一个into_owned方法,可以直接把自己变成Owned那个类型本书,并把所有权转移给调用者。
