业务驱动技术,技术衍生思想~
编程技巧
宏编程并没有什么大不了的,抛开 quote/unquote,它主要的工作就是把一棵语法树转换成另一颗语法树,而这个转换的过程深入下去,不过就是数据结构到数据结构的转换而已。所以一句话总结:宏编程的主要流程就是实现若干 F rom 和 TryFrom,是不是很简单。
在 Rust 中,通过 Trait,可以很方便做 控制反转(Inversion of Control)。
IoC 也可以理解为把流程的控制从应用程序转移到框架之中。以前,应用程序掌握整个处理流程;现在,控制权转移到了框架,框架利用一个引擎驱动整个流程的执行,框架会以相应的形式提供一系列的扩展点,应用程序则通过定义扩展的方式实现对流程某个环节的定制,“框架Call应用”。基于MVC的web应用程序就是如此。
一般我们在做一个库的时候,不会把内部使用的数据结构暴露出去,而是会用自己的数据结构包裹它。
关注点分离(Separation of Concerns),是控制软件复杂度的法宝。Rust 标准库中那些经过千锤百炼的 trait,就是用来帮助我们写出更好的、复杂度更低的代码。
编程思想
- 软件领域有个著名的格林斯潘第十定律:
任何 C 或 Fortran 程序复杂到一定程度之后,都会包含一个临时开发的、不合规范的、充满程序错误的、运行速度很慢的、只有一半功能的 Common Lisp 实现。
类比:
任何 API 接口复杂到一定程度后,都会包含一个临时开发的、不合规范的、充满程序错误的、运行速度很慢的、只有一半功能的 SQL 实现。
数据转换是编程活动中非常重要的部分,我们日常写代码时,主要在处理什么?绝大多数处理逻辑是把数据从一个接口转换成另一个接口。
好的代码,应该是每个主流程都清晰简约,代码恰到好处地出现在那里,让人不需要注释也能明白作者在写什么。
这意味着,我们要把那些并不重要的细节封装在单独的地方,封装的粒度一次写完、基本不需要再改动为最佳,或者即使改动,它的影响也非常局部。
这样的代码,方便阅读、容易测试、维护简单,处理起来更是一种享受。Rust 标准库的 From/TryFrom ,就是处于这个目的设计的,非常值得我们好好使用。
优秀的设计一定是产生简单易读的代码,而不是相反。
作为开发者,在工作中常常能体会到:恰到好处地限制,反而会释放无穷的创意和生产力。
类型
数据类型
array
数组,固定大小的 同构序列,语法:[T; N]
bool
布尔值,true, false
char
utf-8 字符
fn
函数指针,示例:fn(&str) -> usize
i8/i16/i32/i64/i128/isize
有符号整数
u8/u16/u32/u64/u128/usize
无符号整数
pointer
裸指针,*const T, *mut T,注意:裸指针在解引用时是不安全的
示例:
- 1
- 2
- 3
- 4
- 5
let x = 42;
let mut y = 24;
let raw = &x as *const i32;
let raw_mut = &mut y as *mut i32;
COPY
reference
引用,&T, &mut T
slice
切片,动态大小的连续序列,用[T]表述。
一般使用其引用&[T], &mut[T] 或 Box<[T]>
str
字符串切片,一般使用其引用&str, &mut str
tuple
元组,固定大小的异构序列,表述为(T, U, ...)
unit
() 类型,表示没有值
never
!类型,表示类型无法产生任何值。注意:目前还是实验特性
示例:
- 1
- 2
Result<T, !> # 永远不会出错
Result<!, ConnectionError> # 一个循环要么出错退出,要么永不返回
COPY
组合类型 和 自定义类型
Box<T>
分配在堆上的类型 T
Option<T>
T要么存在,要么为None
Result<T, E>
要么成功 Ok(T), 要么失败Err(E)
示例:
- 1
- 2
- 3
Ok(42)
Err(ConnectionError::TooMany)
COPY
Vec<T>
可变列表,分配在堆上
String
字符串
HashMap<K, V>
哈希表
示例:
- 1
let map: HashMap<&str, &str> = HashMap::new();
COPY
HashSet<T>
集合
示例:
- 1
let set: HashSet<u32> = HashSet::new();
COPY
RefCell<T>
为 T 提供内部可变性的智能指针。
示例:
- 1
- 2
- 3
let v = RefCell::new(42);
let mut borrowed = v.borrow_mut();
COPY
Rc<T> / Arc<T>
为T 提供引用计数的智能指针
struct
结构体
enum
标签联合
类型推导
Rust 编译器需要足够的上下文来进行类型推导, 所以有些情况下,编译器无法推导出合适的类型。
在泛型函数后使用
::<T>
来强制使用类型T, 这种写法被称为 turbofish.
泛型函数
- 定义:
在声明一个函数时,可以不指定具体的参数或返回值的类型,由泛型参数来代替, 对函数而言,这是更高阶的抽象。
- 泛型函数,Rust会进行单态化(monomorphization)处理,在编译时,把所有用到的泛型函数的泛型参数展开,生成若干个函数。
单态化的好处是,泛型函数的调用是静态分派(static dispatch),在编译时就一一对应,既保有多态的灵活性,又没有任何效率的损失,和普通函数调用一样高效。
单态化的不足是:
- 编译速度很慢,一个泛型函数,编译器需要找到所有用到的不同类型,一个个编译,所以 Rust 编译代码的速度总被人吐槽,这和单态化脱不开干系(另一个重要因素是宏)
- 单态化,代码以二进制分发会损失泛型的信息;
要点
在给类型添加约束时,可以逐步添加,可以让约束只出现在它不得不出现的地方,这样代码的灵活性最大。
任何语言的学习离不开:「精准学习 + 刻意练习」。
精准学习:
- 深挖一个个高大上的表层知识点,回归底层基础知识的本原,再使用「类比、联想」等方法,打通涉及的基础知识;
- 从「底层设计」往表层实现,一层层构建知识体系,这样「撒一层土,夯实,再撒一层」,让你对知识点理解得「更透彻、掌握得牢固」。
- (回归本原的重要性)第一性原理: 回归事物最基础的条件,将其拆分成基本要素解构分析,来探索要解决的问题。
值在内存中的访问规则:堆 和 栈
通过练习发现学习过程中的「不自知问题」,让自己从「我不知道我不知道」走向「我知道我不知道」,并在下一个循环中弥补知识的漏洞。
治学的方法:博学之,审问之,慎思之,明辨之,笃行之 —— 《中庸》— 子思。
成长环:学习 — 构建 — 反思。
学好任意一门编程语言:首先要吃透涉及的概念,因为编程语言,不过是这些概念的「具体表述和载体」。
算法 + 数据结构 = 程序 —— Pascal Creator, Niklaus Wirth(尼古拉斯·沃斯,图灵奖得主)。
深度掌握「类型系统」,才能随心所欲地使用 Rust 为系统构建数据结构。
在Rust中,可以:
- 使用 Trait 做接口设计;
- 使用泛型做编译期「多态」;
- 使用 Trait Object 做「运行时多态」。
tips:用好Trait 和 泛型,可以非常高效地解决复杂的问题。
- unsafe rust:
所谓 unsafe,不过是把 Rust 编译器在编译器做的严格检查退步成为 C++ 的样子,由开发者自己为其所撰写的代码的正确性做担保。
- FFI,rust 和 其它语言互通操作的桥梁。
掌握好 FFI,你就可以用 Rust 为你的 Python/JavaScript/Elixir/Swift 等主力语言在关键路径上提供更高的性能,也能很方便地引入 Rust 生态中特定的库。
- Fearless Concurrency:
- atomics
- mutex
- Semaphore
- Channel
- actor model
- 想真正把语言融会贯通,还要靠大风大浪中的磨炼。
参考资料
格林斯潘第十定律 - https://zh.wikipedia.org/wiki/格林斯潘第十定律
评论区
写评论
登录
所以,就随便说点什么吧...
这里什么都没有,快来评论吧...