EdwardElricNavigate back to the homepage

Tagless Final in Rust

Edward Elric
July 18th, 2020 · 1 min read

总所周知学习 Java 逃不开对各类设计模式的理解运用。今天千里冰封介绍了一个全新的设计模式——“Tagless Final” Style, 它可以用 trait 在 Rust 中模拟子类型。

第一步实现目标

实现一个 expr 范型方法,可以根据类型执行。如下方代码,可以已 u32 或者 String 的类型返回

1fn main() {
2 fn expr<T: Expr>() -> T {
3 Expr::add(
4 Expr::div(Expr::lit(100), Expr::lit(10)),
5 Expr::sub(Expr::lit(223), Expr::lit(23)),
6 )
7 }
8 println!("{}", expr::<u32>());
9 println!("{}", expr::<String>());
10}

output:

1/Users/edward/.cargo/bin/cargo run --color=always --package tagless --bin tagless
2 Compiling tagless v0.1.0 (/Users/edward/github/SASUKE40/tagless)
3 Finished dev [unoptimized + debuginfo] target(s) in 0.39s
4 Running `target/debug/tagless`
5210
6((100 / 10) + (223 - 23))

基础的 Expr trait 定义

这个最基础的 trait 是外部引入,不能更改的。

1pub(crate) trait Expr {
2 fn lit(i: u32) -> Self;
3 fn add(lhs: Self, rhs: Self) -> Self;
4 fn sub(lhs: Self, rhs: Self) -> Self;
5 fn mul(lhs: Self, rhs: Self) -> Self;
6 fn div(lhs: Self, rhs: Self) -> Self;
7}

实现 String 和 u32 的 impl

1impl Expr for u32 {
2 fn lit(i: u32) -> Self {
3 i
4 }
5 fn add(lhs: Self, rhs: Self) -> Self {
6 lhs + rhs
7 }
8 fn sub(lhs: Self, rhs: Self) -> Self {
9 lhs - rhs
10 }
11 fn mul(lhs: Self, rhs: Self) -> Self {
12 lhs * rhs
13 }
14 fn div(lhs: Self, rhs: Self) -> Self {
15 lhs / rhs
16 }
17}
18
19impl Expr for String {
20 fn lit(i: u32) -> Self {
21 i.to_string()
22 }
23 fn add(lhs: Self, rhs: Self) -> Self {
24 format!("({} + {})", lhs, rhs)
25 }
26 fn sub(lhs: Self, rhs: Self) -> Self {
27 format!("({} - {})", lhs, rhs)
28 }
29 fn mul(lhs: Self, rhs: Self) -> Self {
30 format!("({} * {})", lhs, rhs)
31 }
32 fn div(lhs: Self, rhs: Self) -> Self {
33 format!("({} / {})", lhs, rhs)
34 }
35}

以上的内容都单独抽出放入到 expr.rs 中。

给 expr 增加 exp 方法

因为 Expr trait 不可被更改,所以新增一个 trait 的 TypeParamBounds1: Rust traits

1Syntax
2Trait :
3 unsafe? trait IDENTIFIER Generics? ( : TypeParamBounds? )? WhereClause? {
4 TraitItem*
5 }

定义一个新的 ExprWithExp trait,并实现 u32String 类型的 exp 方法:

1trait ExprWithExp: Expr {
2 fn exp(lhs: Self, rhs: Self) -> Self;
3}
4
5impl ExprWithExp for u32 {
6 fn exp(lhs: Self, rhs: Self) -> Self {
7 lhs.pow(rhs)
8 }
9}
10
11impl ExprWithExp for String {
12 fn exp(lhs: Self, rhs: Self) -> Self {
13 format!("({} ^ {})", lhs, rhs)
14 }
15}

这样就可以给 u32String 拓展出 exp 方法

1fn main() {
2 // snippet
3 fn expr2<T: ExprWithExp>() -> T {
4 ExprWithExp::exp(expr(), Expr::lit(2))
5 }
6
7 println!("{}", expr2::<u32>());
8 println!("{}", expr2::<String>());
9}

output:

1/Users/edward/.cargo/bin/cargo run --color=always --package tagless --bin tagless
2 Compiling tagless v0.1.0 (/Users/edward/github/SASUKE40/tagless)
3 Finished dev [unoptimized + debuginfo] target(s) in 0.56s
4 Running `target/debug/tagless`
5210
6((100 / 10) + (223 - 23))
744100
8(((100 / 10) + (223 - 23)) ^ 2)

Join our email list and get notified about new content

Be the first to receive our latest content with the ability to opt-out at anytime. We promise to not spam your inbox or share your email with any third parties.

More articles from Edward Elric

Recoil 一个基于 actor 模型的 React 状态管理库

前言 最近在 React Europe 2020 Conference 上, facebook 内部释出一个状态管理库 Recoil 通过官方的宣传以及初步的使用,Recoil 在处理 shared state 上比较方便,也能做到最小度的更新来提升复杂 App…

May 16th, 2020 · 2 min read

对比 Vue Composition API 和 React Hooks

引言 最近 Vue 3.0 发布了 Beta 版本,其中最引人注意的就是其 Composition API 。而这个设计近期经常被拿来和 React Hooks 进行比较,以下是两个代码片段: 我们可以看到两者的心智模型非常不同,React…

May 10th, 2020 · 1 min read
© 2015–2020 Edward Elric
Link to $https://twitter.com/sasuke688848Link to $https://github.com/sasuke40Link to $https://www.linkedin.com/in/sasuke/