Effective Rust学习笔记
newtype模式
    newtype是Rust的一种设计模式,通过newtype模式,我们可以在不引入额外的开销的情况下,为一个类型增加新的方法和特性。newtype模式的实现方式是通过struct结构体,将原有的类型作为struct的一个字段,然后为这个struct实现新的方法。
struct Wrapper(Vec<String>);
impl Wrapper {    fn new() -> Self {        Wrapper(Vec::new())    }
    fn push(&mut self, value: String) {        self.0.push(value);    }
    fn pop(&mut self) -> Option<String> {        self.0.pop()    }}
fn main() {    let mut w = Wrapper::new();    w.push("Hello".to_string());    w.push("World".to_string());    println!("{:?}", w.pop());}    上述例子中,我们定义了一个Wrapper结构体,Wrapper结构体中仅包含了一个Vec<String>类型的字段,然后我们为Wrapper结构体实现了new、push、pop方法。通过newtype模式,我们可以在不引入额外的开销的情况下,为Vec<String>类型增加新的方法。
    在上述例子中,我们巧妙地使用newtype模式,避免了Rust Trait的孤儿规则。
    回顾一下孤儿规则,当我们需要为一个类型实现某个Trait时,这个Trait和这个类型必须至少有一个是本地的。如果我们直接使用Vec<String>类型,我们需要为Vec<String>类型实现新的方法,这样会导致我们的代码违反孤儿规则,进而无法正常编译。通过newtype模式,我们可以将Vec<String>类型的方法封装到Wrapper结构体中,这样可以更好的组织代码。
    如果我们直接使用Vec<String>类型,我们需要为Vec<String>类型实现新的方法,这样会导致Vec<String>类型的方法过多,不利于代码的维护。通过newtype模式,我们可以将Vec<String>类型的方法封装到Wrapper结构体中,这样可以更好的组织代码。
常用的newtype模式
- 语义化入参
 
struct UserId(u32);
fn process_user_id(id: UserId) {    // ...}// 使用process_user_id(UserId(1)); // 显式的表明这是一个UserId类型- 语义化返回值
 
struct User {    id: u32,    name: String,}
struct UserWithId(User);
fn get_user(id: u32) -> UserWithId {    UserWithId(User {        id,        name: "Alice".to_string(),    })}- 避免孤儿规则 见上述例子。
 
newtype的缺点
    newtype模式的缺点是:
- 当我们定义的
newtype类型需要和原有类型进行转换时,需要手动实现From和IntoTrait。 - 当我们需要为
newtype类型实现原有类型的所有方法时,需要手动实现Deref和DerefMutTrait。 
总结
newtype适合在问题规模较小的情况下使用,当问题规模较大时,newtype模式会导致代码的冗余和维护困难。
Builder模式
    Rust创建结构体总是需要传入所有的字段,这样会导致结构体的创建过程变得复杂。即使我们实现了#[derive(Debug, Default)] Trait,当某个字段没有实现Default Trait时,我们仍然需要手动传入所有的字段。Builder模式可以解决这个问题,通过Builder模式,我们可以逐步构建结构体,最后再创建结构体。
#[derive(Debug)]struct User {    id: u32,    name: String,    age: u32,}
struct UserBuilder {    id: Option<u32>,    name: Option<String>,    age: Option<u32>,}
impl UserBuilder {    fn new() -> Self {        UserBuilder {            id: None,            name: None,            age: None,        }    }
    fn id(&mut self, id: u32) -> &mut Self {        self.id = Some(id);        self    }
    fn name(&mut self, name: String) -> &mut Self {        self.name = Some(name);        self    }
    fn age(&mut self, age: u32) -> &mut Self {        self.age = Some(age);        self    }
    fn build(&self) -> User {        User {            id: self.id.unwrap(),            name: self.name.clone().unwrap(),            age: self.age.unwrap(),        }    }}
fn main() {    let user = UserBuilder::new()        .id(1)        .name("Alice".to_string())        .age(20)        .build();    println!("{:?}", user);}    Builder模式很受开发者喜爱,常见于各种库中。同时,Builder模式也非常像Java中的Builder模式,通过Builder模式,我们可以逐步构建结构体,最后再创建结构体。
Builder链式构建
    在一般场景下,我们构建一个复杂的结构体往往不能通过使用Builder的new方法一次构造完成。我们可以通过Builder链式构建的方式,逐步构建结构体。
let PersonObj = PersonBuilder::new()    .name("Alice")    .age(20)    .build();// 需要注意的是,我们实现链式的方法,不能消耗self,所以我们返回的是一个&mut self。// build()方法,有两种设计,一种是消耗self,语义是消耗所有资源,build后不允许在再通过此前填入的数据构造新的对象。// 另一种是不消耗self,语义是构造一个对象,但是不消耗资源,可以通过此前填入的数据构造新的对象。特征对象
    特征对象是实现了某种Trait的对象,通过特征对象,我们可以将Trait对象化,进而可以通过Trait对象调用Trait的方法。特征对象的实现方式是通过Box<dyn Trait>,Box<dyn Trait>是一个Trait对象,通过Box将Trait对象包装成一个堆上的对象。
    为什么需要特征对象。请看下面示例代码:
trait Animal {    fn name(&self) -> String;}// 错误的写法fn print_name(animal: Animal) { // error: the size for values of type `dyn Animal` cannot be known at compilation time    println!("{}", animal.name());}print_name函数接收一个实现了Animal特征的参数。但因为Animal是一个Trait,Trait是一个动态大小的类型,所以编译器无法确定Animal的大小,导致编译错误。
    为了解决上述问题,我们可以通过特征对象的方式,将Animal包装成一个堆上的对象,进而解决编译错误。
fn print_name(animal: Box<dyn Animal>) {    println!("{}", animal.name());}    Box<dyn Trait>是一个特征对象的实现方式,dyn关键字后跟Trait,dyn Trait是特征对象的类型。通过Box将Trait对象包装成一个堆上的对象,进而解决编译错误。
特征对象和泛型参数的对比
特征对象和泛型参数都是为了实现类型不同的多态效果,但是两者在设计的根本上有所不同,导致了后续一系列的限制。
    特征的设计理念是将行为和数据分离,我们定义的Trait可以提供到外部使用,由外部实现,这就造成了Trait对象的大小不确定,无法在编译时确定大小,所以我们需要通过Box将Trait对象包装成一个堆上的对象。
    而泛型参数的设计理念是将行为和数据结合在一起,我们定义的泛型参数是在编译时确定大小的,所以泛型参数可以直接使用,不需要通过Box包装。
特征对象的动态特性
    我们需要对特征来实现多态的效果,那么我们就需要使用虚表vtable来实现。vtable是一个函数指针表,通过vtable我们可以实现动态分发,进而实现多态的效果。
在线程闭包函数中使用引用的正确方法
创建线程的函数约束为:
所以传入函数的引用的声明周期必须为’static,否则会出现编译错误。此时我们需要传递一个动态生命周期的引用,我们可以通过Arc<T>来解决这个问题。
use std::{sync::Arc, thread};
const STATIC_STR: &str = "Hello, world!";
#[cfg(predicate = "false")]fn test_thread_life(){    let str: String = String::from("Hello, world!");    // 1. 【可以】在闭包中使用静态声明周期的引用    let _ = thread::spawn(|| {        println!("{}", &STATIC_STR);    });    // 2. 【错误】在闭包中使用动态声明周期的引用    let _ = thread::spawn(|| {        println!("{}", &str); // error: `str` does not live long enough    });
    // 2.1 【正确的】使用Arc<T>解决上述问题    // let arc_str = Arc::new(&str);    let arc_str = Arc::new(str.clone());    let _ = thread::spawn(move || {        println!("{}", arc_str);    });}