Rust Utility Traits

本文是对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()
 }
 }
}

Rust Iterators

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

一个简单的例子

1
2
3
4
5
6
7
8
9
//normal style   
let mut sum = 0;
for i in 0..=10 {
        sum += i;
}
println!("{}", sum);
//iterator style
let sum = (0..=10).fold(0, |sum, i| sum+i);
println!("{}", sum);

以上两种求和的实现是等价, 基于iterator的实现风格对于函数式编程是很常见的, rust也提倡这种方式的写法,不同的是,rust编译器在编译的时候会对这种表达方式进行优化,达到一种zero cost abstraction的效果, 比如以上的求和在编译器优化后可能汇编代码使用11*10/2这种数学公式来执行。这个意思是不用太担心iterator风格的性能问题, rust编译器会进行必要的优化,使得我们使用的时候表达能力与性能兼顾

IntoInterator和Iterator

在rust中iterator最基本的两个trait是IntoInterator和Iterator,他们的定义如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
trait Iterator {
 type Item;
 fn next(&mut self) -> Option<Self::Item>;
 ... // many default methods
}

trait IntoIterator where Self::IntoIter:Iterator<Item=Self::Item> {
 type Item;
 type IntoIter: Iterator;
 fn into_iter(self) -> Self::IntoIter;
}

IntoIterator用来生成Iterator, 实现Iterator可以进行相应的操作, Iteratortrait包含了很多函数, 但是需要实现的只有一个next.

值得一提的是IntoIterator的into_iter使用传值的方式,也就表明了这个方法会将原始类型给消费掉, 进行Copy或者Move, 比如

1
2
3
4
let v = vec![1,2,3];
 //into_iter() move the vecotr v
let iter = v.into_iter();
println!("{}", v[0]);

以上的代码不能编译通过, 因为v已经被Move了, 你可以使用引用来避免这个问题

1
2
3
4
5
let v = vec![1,2,3];
 //以下两种方式等价
let iter = (&v).into_iter();
let iter2 = v.iter();
println!("{}", v[0]);

由于这个使用方式很常见, 所以Vec直接提供了方法Vec<T>::iter(), 他会返回一个生成&T类型的Iterator

rust中的for loop其实是使用Iterator来实现的,以下的代码是等价的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 let v = vec![1,2,3,4];
 for i in &v {
     println!("{}", i);
 }

 //translate to this by compiler
 let mut iter = v.iter();
 while let Some(i) = iter.next() {
     println!("{}", i);
 }

很多时候可以使用IntoIterator作为泛型的边界来使用,比如

1
2
3
4
5
6
7
8
9
use std::fmt::Debug;
fn dump<T, U>(t: T)
 where T: IntoIterator<Item=U>,
 U: Debug
{
 for u in t {
 println!("{:?}", u);
 }
}

在这个例子里面,表示T必须是一种可以产生Iterator, 这个Iterator<U>产出的类型U必须是实现了Debug的

drain方法

很多collection提供了drain方法, 它通过collection的可变引用返回一个Iterator, 当这个Iteratro drop之后, 这些被包含的元素就从colleciton当中移除了

1
2
3
4
5
 let mut v = vec![1,2,3,4];
 {
     let drain_iter = v.drain(2..4);
 }
 assert_eq!(&v, &vec![1,2]);

标准库应用

Iterator在标准库中有广泛的应用, 不止在collection中, 还在很多其他方面, 比如io当中, 使用Iterator来处理io流, 尤其在异步io中。

另外对Iterator风格代码的使用, 对于非函数式编程习惯的人员来说有一定的门槛,需要多练习才能适应,下面将重点介绍以下Iterator适配器

Iterator适配器

rust的Iterator适配器非常灵活,下面将逐个介绍

map and filter

这两个应该很好理解

统一处理Vec中的元素

1
2
3
4
5
6
let text = " ponies \n giraffes\niguanas 
    \nsquid".to_string();
let v: Vec<&str> = text.lines()
    .map(str::trim) ////map的FnMut类型为FnMut(I::Item) -> B, Item由上一个Iterator决定, B由这个FnMut决定
    .collect();
assert_eq!(v, ["ponies", "giraffes", "iguanas", "squid"]);

过滤Vec中的元素

1
2
3
4
5
6
7
let text = " ponies \n giraffes\niguanas 
\nsquid".to_string();
let v: Vec<&str> = text.lines()
 .map(str::trim)
 .filter(|s| *s != "iguanas") //FnMut(&Self::Item) -> bool, Item由上一个Iterator决定
 .collect();
assert_eq!(v, ["ponies", "giraffes", "squid"]);

需要注意的是

  1. rust中adaptor是lazy的,也就是说不管chain了多少个adaptor,这些操作只有被消费的时候才执行,具体就是只有adaptor调用了collect的时候, 操作才被真正执行
  2. adaptor是zero cost abstraction, 这意味着rust编译器会将这些chain内联到最后消费的地方, 比如执行的时候就像是一个for循环语句一样,这样做可以明显的提高执行效率
filter_map and flat_map

filter_map有点像map和filter的组合体, 它要么转换一个值,要么丢弃一个值,使用filter_map有时候比使用组合方式更加方便和优雅, 比如只有在你转换一个值的时候才能决定是否要丢弃一个值

1
2
3
4
5
6
7
8

let text = "1\nfrond .25 289\n3.1415 estuary\n";
for number in text
 .split_whitespace()
 .filter_map(|w| f64::from_str(w).ok())
{
 println!("{:4.2}", number.sqrt());
}

在上个例子中, 你只有做数字转换的时候才知道要不要保留这个值

flat_map就是在每个FnMut中制造出一个Iterator, 然后再把这些Iterator合并起来, 当然这些都是语义层面的, 真正执行的时候, 编译器会做相应的优化, 可能并不会真的做Iterator合并

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    let mut major_cities = HashMap::new();
major_cities.insert("Japan", vec!["Tokyo", "Kyoto"]);
major_cities.insert("The United States", vec!["Portland",
"Nashville"]);
major_cities.insert("Brazil", vec!["São Paulo", "Brasília"]);
major_cities.insert("Kenya", vec!["Nairobi", "Mombasa"]);
major_cities.insert("The Netherlands", vec!["Amsterdam",
"Utrecht"]);
let countries = ["Japan", "Brazil", "Kenya"];
for &city in countries.iter().flat_map(|country|
&major_cities[country]) {
 println!("{}", city);
}
flatten

flatten就是值将Iterator中的每个元素也当成Iterator,然后取第二级中的元素作为输出, flatten通常是值将2层级变为1层级的操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let mut parks = BTreeMap::new();
parks.insert("Portland", vec!["Mt. Tabor Park", "Forest Park"]);
parks.insert("Kyoto", vec!["Tadasu-no-Mori Forest", "Maruyama
Koen"]);
parks.insert("Nashville", vec!["Percy Warner Park", "Dragon
Park"]);
// Build a vector of all parks. `values` gives us an iterator producing
// vectors, and then `flatten` produces each vector's elements in turn.
let all_parks: Vec<_> =
parks.values().flatten().cloned().collect();
assert_eq!(all_parks,
 vec!["Tadasu-no-Mori Forest", "Maruyama Koen", "Percy
Warner Park",
 "Dragon Park", "Mt. Tabor Park", "Forest Park"])

上面的示例将Vec元素打平成&str作为结果, 3个Vec变成了6个&str

flatten与Option配合使用将会有很妙的效果

1
2
3
4
5
assert_eq!(vec![None, Some("day"), None, Some("one")]
 .into_iter()
 .flatten()
 .collect::<Vec<_>>(),
 vec!["day", "one"]);

上面的示例将Vec<Option>变成了Vec<&str>, 这是因为Option本生也实现了IntoIterator 返回了非None的值,同样的方式也适用于Result

有的时候flatten与map的组合其实就等于flat_map, 这种情况下使用flat_map会显得更方便与直接

其他

其他还有很多常用的适配器,比如take, skip, peekable, fuse, chain 等等,暂且不做讨论

Rust Struct

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

Struct

基本用法

在rust中, struct分为三种类型,分别为named, tuple-like, unit-like

1
2
3
4
5
6
7
8
9
struct User {
    id: usize,
    name: String,
    manager: Option<Rc<User>>,
}

struct Point(usize, usize);

struct empty;

关联方法可以使用self, &self, &mut self, Box<Self>, Rc<Self>等self参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
impl User {
    fn new(id: usize, name: String) -> Self {
        User { id: id, name: name, manager: None }
    }

    fn get_name(&self) -> String {
        self.name.clone()
    }

    fn set_name(&mut self, name: String) {
        self.name = name;
    }

    fn move_user(self) -> User {
        self
    }

    fn manage(self: Rc<Self>, user: &mut User) {
        user.manager = Some(self);
    }
}

可以这样调用这些方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let mut member = User::new(1, "jared".to_string());
    let manager = User::new(2, "jerry".to_string());
    //&mut self
    member.set_name("abc".to_string());
    //&self
    member.get_name();
    //Rc<Self>
    Rc::new(manager).manage( &mut member);
    //Box<Self>
    let m2 = Box::new(member);
    m2.get_name();
}

调用函数时, 类型会自动转换(implicit conversion), 当然是在满足规则的情况下, 一个&self是没办法自动转换成一个&mut self, 相反则是成立的

内部可变性

有的时候, 你需要在程序中共享一些struct, 比如说配置参数,由于使用Rc, 这个时候这个配置结构是不可变的。但是很多时候, 你还是希望可以修改它的某些属性, 这个时候就要用到内部可变性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//配置参数
struct Config {
    path: RefCell<String>,
    timeout: 5,
}

//实例
struct Instance {
    conf: Rc<RefCell<Config>>
}

//共享配置参数
 let config = Rc::new(Config{path: RefCell::new("/abc".to_string()),timeout: 5});
 let mut ins = Instance{conf: config.clone()};
 let ins2 = Instance{conf: config};

//Config本身是immutable的, 但是可以通过内部可变性,改变path的值
 let _ = ins.conf.path.borrow_mut().replace("abc", "xyz") ;
 println!("{}", ins2.conf.path.borrow());

内部可变性的问题在于, 它打破了rust编译期的借用规则, 但是在运行期,如果它同时存在一个可变引用与不可变引用,程序会panic, 也就是说在运行期它依然要遵循借用规则