Effective Rust学习笔记
newtype
模式
newtype
是Rust
的一种设计模式,通过newtype
模式,我们可以在不引入额外的开销的情况下,为一个类型增加新的方法和特性。newtype
模式的实现方式是通过struct
结构体,将原有的类型作为struct
的一个字段,然后为这个struct
实现新的方法。
上述例子中,我们定义了一个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
模式
- 语义化入参
- 语义化返回值
- 避免孤儿规则 见上述例子。
newtype
的缺点
newtype
模式的缺点是:
- 当我们定义的
newtype
类型需要和原有类型进行转换时,需要手动实现From
和Into
Trait。 - 当我们需要为
newtype
类型实现原有类型的所有方法时,需要手动实现Deref
和DerefMut
Trait。
总结
newtype
适合在问题规模较小的情况下使用,当问题规模较大时,newtype
模式会导致代码的冗余和维护困难。
Builder
模式
Rust
创建结构体总是需要传入所有的字段,这样会导致结构体的创建过程变得复杂。即使我们实现了#[derive(Debug, Default)]
Trait,当某个字段没有实现Default
Trait时,我们仍然需要手动传入所有的字段。Builder
模式可以解决这个问题,通过Builder
模式,我们可以逐步构建结构体,最后再创建结构体。
Builder
模式很受开发者喜爱,常见于各种库中。同时,Builder
模式也非常像Java
中的Builder
模式,通过Builder
模式,我们可以逐步构建结构体,最后再创建结构体。
Builder
链式构建
在一般场景下,我们构建一个复杂的结构体往往不能通过使用Builder
的new
方法一次构造完成。我们可以通过Builder
链式构建的方式,逐步构建结构体。
特征对象
特征对象是实现了某种Trait的对象,通过特征对象,我们可以将Trait对象化,进而可以通过Trait对象调用Trait的方法。特征对象的实现方式是通过Box<dyn Trait>
,Box<dyn Trait>
是一个Trait对象,通过Box
将Trait对象包装成一个堆上的对象。
为什么需要特征对象。请看下面示例代码:
print_name
函数接收一个实现了Animal
特征的参数。但因为Animal
是一个Trait,Trait是一个动态大小的类型,所以编译器无法确定Animal
的大小,导致编译错误。
为了解决上述问题,我们可以通过特征对象的方式,将Animal
包装成一个堆上的对象,进而解决编译错误。
Box<dyn Trait>
是一个特征对象的实现方式,dyn
关键字后跟Trait
,dyn Trait
是特征对象的类型。通过Box
将Trait
对象包装成一个堆上的对象,进而解决编译错误。
特征对象和泛型参数的对比
特征对象和泛型参数都是为了实现类型不同的多态效果,但是两者在设计的根本上有所不同,导致了后续一系列的限制。
特征的设计理念是将行为和数据分离,我们定义的Trait
可以提供到外部使用,由外部实现,这就造成了Trait
对象的大小不确定,无法在编译时确定大小,所以我们需要通过Box
将Trait
对象包装成一个堆上的对象。
而泛型参数的设计理念是将行为和数据结合在一起,我们定义的泛型参数是在编译时确定大小的,所以泛型参数可以直接使用,不需要通过Box
包装。
特征对象的动态特性
我们需要对特征来实现多态的效果,那么我们就需要使用虚表vtable
来实现。vtable
是一个函数指针表,通过vtable
我们可以实现动态分发,进而实现多态的效果。
在线程闭包函数中使用引用的正确方法
创建线程的函数约束为:
所以传入函数的引用的声明周期必须为’static,否则会出现编译错误。此时我们需要传递一个动态生命周期的引用,我们可以通过Arc<T>
来解决这个问题。