Rust类型驱动开发
- 什么是类型驱动开发
- Rust类型驱动开发的实际应用
- Rust中类型驱动开发的简单举例
- Rust中类型驱动开发的更具体的示例
- Rust 中类型驱动开发Api
- 总结
什么是类型驱动开发
type-diven development,即将开发者需要的逻辑约束条件编码到类型系统中,举个例子,一般业务逻辑下开发者通常需要检查输入的正确性,一般情况下开发者的思维惯性会在调用function的内部做一些逻辑判断,比如字符串长度是否符合条件,格式是否符合如人名,邮箱地址等等。类型驱动开发是将此类检查放在形成某一种类型的时候完成,即如果逻辑不通则不会形成需要的类型,同样的,类型驱动开发也具备一定的封装性,保证开发者不会因为误用而破坏流程的安全性和产生一些垃圾数据。
Rust类型驱动开发的实际应用
Rust中类型驱动开发的简单举例
代码示例:
use unicode_segmentation::UnicodeSegmentation;struct Address(String);impl Address {pub fn TryParseEmail(s: String) -> Address {let is_empty = s.trim().is_empty();let is_too_long = s.graphemes(true).count() > 256;let forb_charc = ['/', '(', ')', '"', '<', '>']; //and so onlet is_legal = s.chars().any(|c| forb_charc.contains(&c));if (is_legal || is_empty || is_too_long) {panic!("{} is not a valid address", s);} else {Address(s)}}
}fn main() {let s: String = "asdfasdf//s".to_string();Address::TryParseEmail(s);
}
如上述代码,当程序收集到了一个地址字符串,TryPaseEmail尝试将它转换成一个Address类型,无法形成Address类型的字符串即可标记为不合法的地址,从而抛出错误中断线程。需要注意的是,这里使用的是结构体元组,也就是指定的类型,从而无需在所有需要地址的位置都去判断是否为空,是否合法等操作,这对于大型的程序来说十分友好。
Rust中类型驱动开发的更具体的示例
Rust 中类型驱动开发Api
源代码参考视频地址
此代码实现一个类似进度条的打印,通过trait限定可使用的范围,在编译期间便可抛出更加精准的错误,相较于C++的模板编程,只有在使用到了这段代码的程序才能发现这段代码的问题来讲更加的迅速和及时,而相较于python这种动态语言来讲,python给出的错误一般情况下会摸不着头脑或者既不以预期方式运行也没有报错,这样会使得开发者更加的头疼。
代码示例:
use std::{thread::sleep, time::Duration};struct Unbounded;
struct Bounded {bound: usize,delims: (char, char),
}
//上述Unbounded,Bounded即描述迭代器是否有边界,既大小是不是"有限的",有限则在实现的角度会添加括号已示边界。
struct Progress<Iter, Bound> {iter: Iter,i: usize,bound: Bound,
}
//Progress,进度条结构体, 包含迭代器,i表示进度条长度,bound表示边界,边界包含长度和用什么符号来表示边界trait ProgressDisplay: Sized {fn display<Iter>(&self, progress: &Progress<Iter, Self>);
}
//trait ProgressDisplay 需要trait Sized以便Progress在编译期大小可知,类型安全,否则无法通过编译
impl ProgressDisplay for Unbounded {fn display<Iter>(&self, progress: &Progress<Iter, Self>) {println!("{}", "|".repeat(progress.i))}
}
impl ProgressDisplay for Bounded {fn display<Iter>(&self, progress: &Progress<Iter, Self>) {println!("{}{}{}{}",progress.bound.delims.0,"|".repeat(progress.i)," ".repeat(self.bound - progress.i),progress.bound.delims.1);}
}
//分别为有界无界实现打印方式,主要区别,有边界的两边有符号,且长度可知,未遍历到的进度用空格填充
impl<Iter> Progress<Iter, Unbounded> {pub fn new(iter: Iter) -> Self {Progress {iter,i: 0,bound: Unbounded,}}
}
//默认无界
impl<Iter, Bound> Iterator for Progress<Iter, Bound>
whereIter: Iterator,Bound: ProgressDisplay,
{type Item = Iter::Item;fn next(&mut self) -> Option<Self::Item> {self.bound.display(&self);self.i += 1;self.iter.next()}
}
//为进度条实现迭代器,方可迭代,实现关键步骤:Item & next 方法
trait ProgressIteratorExt: Sized {fn progress(self) -> Progress<Self, Unbounded>;
}impl<Iter> ProgressIteratorExt for Iter
whereIter: Iterator,
{fn progress(self) -> Progress<Self, Unbounded> {Progress::new(self)}
}
//便于使用Dot(.progress())将容器初始化为进度条
//ExactSizeIterator implementation
impl<Iter> Progress<Iter, Unbounded>
whereIter: ExactSizeIterator,
{pub fn with_bound(self) -> Progress<Iter, Bounded> {let bound = Bounded {bound: self.iter.len(),delims: ('[', ']'),};Progress {i: self.i,iter: self.iter,bound,}}
}
//with bound 会加上默认边界,并将默认的无边界进度条转为有边界的
impl<Iter> Progress<Iter, Bounded> {pub fn with_delims(mut self, delims: (char, char)) -> Self {self.bound.delims = delims;self}
}fn main() {let brkt = ('<', '>');let vector = vec![1, 2, 3, 4, 5];for _ in vector.iter().progress().with_bound().with_delims(brkt) {sleep(Duration::from_secs(1));}//以下代码将会出错,错误的调用方法,使用无界progress调用需要有界进度条类型的方法。for _ in (0..).progress().with_delims(brkt) {sleep(Duration::from_secs(1));}
}
以上展示的是在开发者自行实现一些组件的时候,通过类型驱动开发可以进一步保证调用的正确性,常规方式用trait限定和明确的类型定义调用与之相关的方法,从而保证Api的安全稳定。
总结
以上便是Rust中的类型驱动开发的粗浅理解,类型驱动开发并不是一个新概念,而是通过Rust的语言特点可以更好的表达,并且在Rust编程中也很常见,有的开发者称之为新类型模式,这通常体现在,Api的入参验证,通过中间方法,如将string转换成特定的邮箱地址类型,人名类型等等,从而使得开发专注于逻辑本身而不是语言限定本身。
“苟日新,日日新,又日新,常看常新”