《rust book2》读书笔记

2018-01-13  本文已影响0人  羊陆之交

《rust book 2》中介绍了一些基础的知识点,例如:引用, 借用, 泛型等等。另外,还有一些平时接触较少,例如:智能指针,trait object,高级生命周期,marker trait (Sync, Send, Sized) , 函数指针 fn 和闭包等。这些特性在特殊场景很有用,同时熟悉这些也能让我们更容易读懂第三方库源码。

第 3 章 通用编程概念

数组 (array) 的数据是分配在栈上,而不是堆上;数组的大小在编译时已确定,运行时不能改变。对于确定长度的集合适合用数组代替 vec,运行效率更高。

第 4 章 理解所有权

  1. 所有权的规则:

图 4-1 为以下代码片段关于所有权的内存模型:

let s1 = "hello".to_string();
let s2 = s1;
4-1 有权的内存模型
  1. 为确保不出现悬空引用,引用必须满足下面两个规则中的一个:

图 4-2 为以下代码片段关于引用的内存模型:

let s1 = "hello".to_string();
let s = &s1;
4-2 引用的内存模型
  1. 切片能引用一个集合中一段连续的元素,图 4-3 为以下代码片段关于切片的内存模型:
 let s = "hello world".to_string();
 let world = &s[6..11];
4-3 切片的内存模型
  1. &String 与 &str 的区别,前者是引用整个字符串,后者是字符串中的连续字符;在函数参数中,建议使用 &str 作为参数类型,因为 &String 类型的变量会通过 deref 隐士转换为 &str,反之则不行。

第 5 章 使用 struct 组织关联的数据

  1. 使用 {:#?} 代替 {:?} 格式化,输出值更可读。

  2. 在对 struct 的方法调用过程中,rust 能自动引用和解引用,使得与方法的第一个参数匹配,这避免了繁琐的显示转换。

struct Foo;
impl Foo {
        fn f(&self) {}
}
let foo = Foo;
foo.f();

在调用 foo 的 f 时,foo 会转换成 &foo:
&foo.f();

第 7 章 使用 mod 复用和组织代码

  1. 可见性规则:

第 8 章 集合

  1. 通过范围索引访问 String,如果范围的边界不在字符 (rust 中的字符是由 UTF-8 编码。) 的边界,会导致程序 panic。可以尝试运行以下代码:
let s = "你好";  
println!("{:?}", s.as_bytes());  
println!("{:?}", &s[0..2]);

如果需要遍历字符串中的字符,需要使用第三方库。

  1. 使用 entry 更新 HashMap 中的值:
let mut map: HashMap = HashMap::new();
let count = map.entry(&"hello".to_string()).or_insert(0);
*count += 1;
  1. 集合的初始化建议使用 with_capacity() 代替 new(),避免集合内存逐步增大过程中的内存拷贝。

第 10 章 泛型、特征、生命周期

  1. 泛型没有运行时开销,编译器在编译期会查找所有调用泛型的代码,为泛型对应的具体类型生成代码。例如:
fn f(i: T) {}
f(1_i32);

编译器会生成:

 f_i32(i: i32) {}

需要注意的是,生命周期属于一种泛型。

  1. 在函数或结构体定义中的生命周期,是为了表示多个引用的生命周期的相互关系,从而避免悬空指针。

  2. 生命周期的省略规则:

 fn f(i: &str, j: &str) {} 和 fn f<'a, 'b>(i: &'a str, j: &'b str) 等价。
fn f(s: &str, i: i32) -> (&str, &str) 和 fn f<'a>(s: &'str, i: i32) -> (&'a str, &'a str) 等价。
fn f(&self, i: &str) -> &str 和 fn f<'a, 'b>(&'a self, i: &'b str) -> &'a str 等价。

第 11 章 测试

  1. 通常,测试都写在各自的 mod 中,测试每个函数的正确性,这种称为单元测试。rust 还支持集成测试,这些测试放在与 src 平级的 test 目录中,集成测试的目的是为了测试 crate 的公有 API 组合调用的正确性。

第 13 章 迭代器和闭包

  1. 每个函数和闭包的类型都不相同,即使两个函数的输入和输出完全相同。函数可以隐士转换为函数指针 fn, 闭包是实现 trait Fn, FnMut, FnOnce 之一的类型。

  2. 使用迭代器比 for 循环的效率会更高一点,熟练后可读性和可维修性也比 for 要好,所以建议优先使用迭代器。

第 15 章 智能指针

  1. Box,是指向分配在堆上数据的指针,占用空间为 usize 的大小 (在 64 位机器上为 64 bytes,32 位机器上为 32 bytes)。使用场景:当类型的大小在编译时无法确认,可以使用。例如:
enum List { Cons(i32, List), None }

由于 List 递归嵌套,编译时会出错,可以用 Box 改写:

enum List { Cons(i32, Box), None }
  1. Rc,是引用计数指针,数据也分配在堆上,可以通过 clone() 将同一份数据和多个 owner 绑定,每 clone() 一次引用计数加 1,当引用计数为 0 时,会自动销毁数据。需要注意的是,只能在单线程中使用。

  2. RefCell,是可以在运行时获得数据的可变性指针,但是在运行时检测可变性有性能开销。同样,也只能在单线程中使用。例如下面的代码,能通过编译,但是在运行时会 panic!。

use std::cell::RefCell;
let s = RefCell::new("hello".to_string());
let r1 = s.borrow_mut();
let r2 = s.borrow_mut();
  1. Rc 和 RefCell 联合使用时,可能出现循环引用,会导致内存泄露。例如:
enum List {
    Cons(i32, RefCell>),
     Nil,
}

为了支持循环引用,同时避免内存泄露,可使用 downgrade 将 Rc 转换成 Weak。在 Rc 上每次调用 clone() 时,会使得引用计数 strong_count 加 1;每次调用 downgrade 与之不同的是,weak_count 加 1,而 strong_count 不变。Rc 只要检测到 strong_count 为 0,即使 weak_count 不为 0,也会释放堆上的内存,从而避免了内存泄露。

15-1 使用 Weak 解决循环引用导致的内存泄露
struct Node {
    value: i32,
    pre: RefCell>,
    next: RefCell>,
}
  1. 智能指针通过解引用 * 操作符,能直接获得数据。例如:
let s = "hello".to_string();
let j = Box::*new*(s.clone());
assert_eq!(s.clone(), *j);

use std::rc::Rc;
let j = Rc::*new*(s.clone());
assert_eq!(s.clone(), *j);

use std::cell::RefCell;
let j = RefCell::*new*(s.clone());
assert_eq!(s, *j.borrow());

能获取数据的原因是,智能指针都实现了 Deref trait,将智能指针隐士转换为数据的引用。assert_eq!(s.clone(), *j); 会转换为:

assert_eq!(s.clone(), *(j.deref())); 其中,j.deref() 返回 &String。

第 16 章 并发

  1. 多线程之间通信时,优先使用 channel,其次才使用 Mutex。因为 channel 的接受方维护了一个数据队列,发送方不会阻塞线程;而 Mutex 则可能由于数据竞争,阻塞线程,另外,也有可能产生死锁。

  2. 上一章提到,在单线程中可以用 Rc,使得一份数据有多个 owner。在多线程中可以用 Arc,达到相同的效果,其中 A 表示原子性 (atomic)。

  3. 关于多线程间数据通信的两个 marker trait: Send, Sync:

第 17 章 trait object

  1. 对于一个 trait Draw,Box 是一个 trait object,表示 Box 里的类型都必须实现 Draw。通过 trait object,可以实现“多态”,在运行时动态分发。比较如下两个结构体:
struct Screen1 {
  components: Vec<Box<Draw>>,
}

struct Screen2<T: Draw> {
  components: Vec<T>, 
}

Screen1 支持实现 Draw 的多种类型,在运行时调用不同类型的方法,有运行时开销;Screen2 的单个实例只支持一种实现 Draw 类型的实例集合,在编译时编译器会生成调用类型的代码(称为 monomorphized),属于静态分发,没有运行时开销。

  1. trait object 需要 trait 是对象安全的,需要同时满足以下两个条件:
    *trait 不能和 Sized 绑定。
    Sized 也是一个 marker trait,表示在编译时就能确定类型的大小,泛型参数会默认和 Sized 绑定;?Sized 表示类型可能是 Sized 也可能不是,triat 会默认和 ?Sized 绑定。这条规则可以这样理解,trait object 在编译时是无法确定堆上内存大小的,如果指定 trait 为 Sized,这两者会相互矛盾。

关于这些规则的解释是,trait object 在编译时会擦除 Self 的具体类型和泛型参数的类型,因此在运行时就无法推断出这些参数的类型。

第 18 章 模式匹配

  1. 一些平时较少用到的语法:
let i = 1;
match i {
    1 | 2 => {}
    _ = {}
}
let i = 1;
match i {
    1 ... 10 => {}
     _ => {}
}

match 的分支可以和条件判断语句组合使用:

let i = 1;
let j = 10;
match i {
    1 if j <= 10 => {}
     _ => {}
}
let t = (1, 2, 3);
let (.., i) = t;

第 19 章 高级特性

  1. 生命周期的高级特性主要有以下三种:生命周期的子类型,生命周期和泛型绑定,trait object 的生命周期。
fn foo<'a, 'b: 'a>(i: &'a str, j: &'b str) {}

其中的 'b:'a 表示 'b 的生命周期比 'a 要长,因此 'b 是 'a 的生命周期子类型。

fn foo<'a, T: 'a>(i: &'a T);

其中的 T:'a 表示 T 中的引用的生命周期比 'a 要长。

trait Foo {}
struct Bar<*'a*> {x: &*'a *i32}
let x = 1;
let bar = Bar {x: &x};
let foo = Box::*new*(bar);

需要注意的是,该场景只适用于在同一语句块内,如果 trait object 作为函数的返回值,那么仍然需要显示指定生命周期为 Box<Foo + 'a>。

  1. 关联类型是指将 trait 和一个类型占位符关联,这样 trait 中的方法的参数能使用该类型占位符。例如:
trait Iterator {
    type Item;
    fn next(&mut self) -> Option;
}

如果使用泛型实现,那么同一类型对同一 trait 能实现多次,关联类型避免了这种情况的发生。

struct Foo {}
trait iterator {
    fn next(&mut self) -> Option
}
impl iterator for Foo { fn next(&mut self) -> int {}}
impl iterator for Foo { fn next(&mut self) -> String {}}
  1. 如果 struct 中的方法和 trait 中的方法重名,那么需要使用下面的方法调用:
trait Foo { fn f(&self); }
struct Bar;
impl Bar {
    fn f(&self, i: i32) {
        println!("i32");
    }
}

impl Foo for Bar {
    fn f(&self) {
        println!("trait")
    }
}

 let b = Bar;
 Foo::f(&b);
  1. 动态大小类型,是指所占的内存只有在运行时才能确定。str 就是动态类型,所以不能直接使用 str,必须要引用 &str。回顾图 4-3,&str 有两个值,一个是指针,另一个是所指数据的长度。另外,trait 也是动态大小类型,所以只能使用 &Trait 或 Box。

  2. 函数能隐士转换为函数指针类型 fn,注意与闭包 Fn trait 的区别。fn 已经实现了 Fn, FnMut, FnOnce,所以一个函数的参数为闭包,可以将一个函数指针传入。

总结

总体而言,《rust book 2》对 rust 的特性介绍得比较全面,除了宏还没有,深入浅出,读起来比较流畅。

上一篇 下一篇

猜你喜欢

热点阅读