如何使我们的计算机语言的类型系统更智能,将类型推理从程序员转移到编译器?在当今,随着系统变得越来越复杂,移动部件越来越多,能够确保每个部件协同工作变得极为重要。
Rust 的借用检查器就是一个很好的例子,对于那些不熟悉的人,以一种简单的方式,Rust 能够推理内存使用情况,抛弃手动内存管理和垃圾收集器。
还有行为类型:这些类型旨在描述程序的行为,而不仅仅是在程序中移动的数据类型。沿着这条路走下去,有两个主要主题,类型状态Typestates和会话类型,现在我只关注类型状态Typestates。
Typestates 的思想是类型应该描述程序的状态,它们可以被描述为有限状态机,使它们易于验证,它们还提供了“调用安全性”,即在使用类型状态时,如果某个方法被调用,程序处于允许这样做的状态。
Java的反面案例
例如,考虑 Java 的 Scanner,没有什么可以阻止您编写以下代码:
Scanner s = new Scanner(System.in); |
这会导致一个 IllegalStateException,我们应该可以做得更好,不是在运行时发现这个错误,而是在编写代码时让编译器发现这个错误:
即在调用 close 之后应该毫无疑问地尝试从 the 中读取Scanner是一个错误。某些 IDE 可能会警告您,但并不是每个人都使用 IDE,也不是每种语言都有。
为了做得更好,我们可以使用类型状态!如果 Java 有类型状态,则上面的示例可能如下所示:
Scanner<Open> sOpen = Scanner(System.in); |
在我们继续之前,重要的是要注意类型状态需要别名控制,也就是说,如果我们有sOpen别名,如果有人想调用nextLine. 需要确保sOpen在close被调用时被调用,但是 Java 无法提供这种机制,而 Rust 可以。
Rust 中的类型状态
使用 Rust 我们将实现比Open/Closed开关更复杂的东西,下面的例子取自论文“Typestates to Automata and back: a tool”.
我们有一个Drone可以处于三种状态:
- Idle — 无人机在地面上。
- Hovering ——无人机停在半空中。
- Flying — 无人机正在从一个地方飞到另一个地方。
可能的转换是:
- 从Idle到Hovering,通过take_off方法。
- 从Hovering到Idle,通过land方法。
- 从Hovering到Flying,在move_to从一个地方到另一个地方的飞行过程中。
- 从Flying到Hovering,在move_to方法之后。
我们可以从定义我们的Drone和可能的状态开始:
struct Idle; |
我们现在需要Drone<State>为每个状态实现:
impl Drone<Idle> { |
请注意,方法通过使用self而不是来使用结构&self,这将启用别名控制,保证实例不被重用。。另一个重要的注意事项是move_to消费self而不是&self背后的原因:因为输入和输出类型匹配,我们应该能够简单地使用引用和返回(),鉴于 typestate在 move_to期间从Hovering到Flying和 变回, self必须被消费使用。
这些From的实现被排除在外,因为它们是从旧结构到新结构的简单分配。
现在我们可以安全地驾驶我们的无人机:
fn drone_flies() { |
如果我们尝试先驾驶无人机take_off或两次降落无人机,编译器将正确指出该方法未实现,如以下示例所示:
fn drone_does_not_fly_idle() { |
error[E0599]: no method named `move_to` found for struct `drone::Drone<drone::Idle>` in the current scope |