25|类型系统:如何围绕trait来设计和架构系统?

220 阅读3分钟

前言

软件开发的整个行为,基本上可以说是不断创建和迭代接口,然后在这些接口上进行实现的过程

正式开始

用 trait 让代码自然舒服好用

  1. 在设计 trait 的时候,除了关注功能,还要注意是否好用、易用
  2. trait 在设计结束之后,不要先着急撰写实现 trait 的代码,而是最好先写一些对于 trait 使用的测试代码

用 trait 做桥接

在 Rust 里,桥接的工作可以通过函数来完成,但最好通过 trait 来桥接


// Engine trait:未来可以添加更多的 engine,主流程只需要替换 engine
pub trait Engine {
    // 生成一个新的 engine
    fn create<T>(data: T) -> Result<Self>
    where
        Self: Sized,
        T: TryInto<Self>,
    {
        data.try_into()
            .map_err(|_| anyhow!("failed to create engine"))
    }
    // 对 engine 按照 specs 进行一系列有序的处理
    fn apply(&mut self, specs: &[Spec]);
    // 从 engine 中生成目标图片,注意这里用的是 self,而非 self 的引用
    fn generate(self, format: ImageOutputFormat) -> Vec<u8>;
}
/*
通过 Engine 这个 trait,我们把第三方的库 photon 和自己设计的 Image Spec 连接起来,使得我们不用关心 Engine 背后究竟是什么,只需要调用 apply 和 generate 方法即可
*/


// 使用 image engine 处理
let mut engine = Photon::create(data)
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
engine.apply(&spec.specs);
let image = engine.generate(ImageOutputFormat::Jpeg(85));

使用 trait 提供控制反转

通过使用 trait,我们可以在设计底层库的时候告诉上层:我需要某个满足 trait X 的数据,因为我依赖这个数据实现的 trait X 方法来完成某些功能,但这个数据具体怎么实现,我不知道,也不关心

控制反转是架构中经常使用到的功能,它能够让调用者和被调用者之间的关系在某个时刻调转过来,被调用者反过来调用调用者提供的能力,二者协同完成一些事情

用 trait 实现 SOLID 原则

  1. SRP:单一职责原则,是指每个模块应该只负责单一的功能,不应该让多个功能耦合在一起,而是应该将其组合在一起。
  2. OCP:开闭原则,是指软件系统应该对修改关闭,而对扩展开放。
  3. LSP:里氏替换原则,是指如果组件可替换,那么这些可替换的组件应该遵守相同的约束,或者说接口。
  4. ISP:接口隔离原则,是指使用者只需要知道他们感兴趣的方法,而不该被迫了解和使用对他们来说无用的方法或者功能。
  5. DIP:依赖反转原则,是指某些场合下底层代码应该依赖高层代码,而非高层代码去依赖底层代码。

资料链接

  1. SOLID定义
  2. crates.io
  3. The Cargo Book
  4. 编译策略

加餐|Rust2021版次问世了!

升级Rust edition到2021

  1. 执行rustup update stable 完成工具链的升级
  2. cargo fix --edition
  3. 修改 Cargo.toml,替换 edition = “2021”
  4. cargo build / cargo test 确保一切正常

Rust如何通过版次解决版本兼容问题

  1. 库的作者还是以旧的版次发布他的代码,使用库的开发者可以选择他们想使用最新的版次,二者可以完全不一致
  2. 编译时,Rust 编译器以旧的版次的功能编译旧的库,而以新的版次编译使用者的代码

Rust 2021 包括了什么新东西?

闭包的不相交捕获

在 edition 2021 之前,哪怕你只用到了其中一个域,闭包也需要捕获整个数据结构,即使是引用。但是 2021 之后,闭包可以只捕获需要的域

feature resolver

Cargo 的默认行为是在依赖中多次引用单个包时合并所用到的功能

image.png

新的prelude

在 2021 版次中,TryInto、TryFrom 和 FromIterator 默认被引入到 prelude 中