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 T
where 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有个好处就是可以在需要的时候才申请内存。
```rust
enum 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那个类型本书,并把所有权转移给调用者。