Rusty类型状态Typestates入门 - rustype

21-12-05 banq

如何使我们的计算机语言的类型系统更智能,将类型推理从程序员转移到编译器?在当今,随着系统变得越来越复杂,移动部件越来越多,能够确保每个部件协同工作变得极为重要。

Rust 的借用检查器就是一个很好的例子,对于那些不熟悉的人,以一种简单的方式,Rust 能够推理内存使用情况,抛弃手动内存管理和垃圾收集器。

还有行为类型:这些类型旨在描述程序的行为,而不仅仅是在程序中移动的数据类型。沿着这条路走下去,有两个主要主题,类型状态Typestates和会话类型,现在我只关注类型状态Typestates。

Typestates 的思想是类型应该描述程序的状态,它们可以被描述为有限状态机,使它们易于验证,它们还提供了“调用安全性”,即在使用类型状态时,如果某个方法被调用,程序处于允许这样做的状态。

 

Java的反面案例

例如,考虑 Java 的 Scanner,没有什么可以阻止您编写以下代码:

Scanner s = new Scanner(System.in);
s.close();
s.nextLine();

这会导致一个 IllegalStateException,我们应该可以做得更好,不是在运行时发现这个错误,而是在编写代码时让编译器发现这个错误:

即在调用 close 之后应该毫无疑问地尝试从 the 中读取Scanner是一个错误。某些 IDE 可能会警告您,但并不是每个人都使用 IDE,也不是每种语言都有。

为了做得更好,我们可以使用类型状态!如果 Java 有类型状态,则上面的示例可能如下所示:

Scanner<Open> sOpen = Scanner(System.in);
// `sOpen.close()` consumes the original `sOpen` and returns a new Scanner
Scanner<Closed> sClosed = sOpen.close();
sClosed.nextLine(); // Type error, Scanner<Closed> does not implement `nextLine`

在我们继续之前,重要的是要注意类型状态需要别名控制,也就是说,如果我们有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;
struct Hovering;
struct Flying;
struct Drone<State> {
    x: f32,
    y: f32,
    state: PhantomData<State>,
}

我们现在需要Drone<State>为每个状态实现:

impl Drone<Idle> {
    pub fn new() -> Self {
        Self {
            x: 0.0,
            y: 0.0,
            state: PhantomData,
        }
    }

    pub fn take_off(self) -> Drone<Hovering> {
        Drone::<Hovering>::from(self)
    }
}

impl Drone<Hovering> {
    fn land(self) -> Drone<Idle> {
        Drone::<Idle>::new()
    }

    fn move_to(self, x: f32, y: f32) -> Drone<Hovering> {
        let drone = Drone::<Flying>::from(self);
        drone.fly(x, y)
    }
}

impl Drone<Flying> {
    fn fly(mut self, x: f32, y: f32) -> Drone<Hovering> {
        self.x = x;
        self.y = y;
        Drone::<Hovering>::from(self)
    }
}

请注意,方法通过使用self而不是来使用结构&self,这将启用别名控制,保证实例不被重用。。另一个重要的注意事项是move_to消费self而不是&self背后的原因:因为输入和输出类型匹配,我们应该能够简单地使用引用和返回(),鉴于 typestate在 move_to期间从Hovering到Flying和 变回, self必须被消费使用。

这些From的实现被排除在外,因为它们是从旧结构到新结构的简单分配。

现在我们可以安全地驾驶我们的无人机:

fn drone_flies() {
    let drone = Drone::<Idle>::new() // Drone<Idle>
        .take_off()                  // Drone<Hovering>
        .move_to(-5.0, -5.0)         // => Drone<Flying> => Drone<Hovering>
        .land();                     // Drone<Idle>
    assert_eq!(drone.x, -5.0);
    assert_eq!(drone.y, -5.0);
}

如果我们尝试先驾驶无人机take_off或两次降落无人机,编译器将正确指出该方法未实现,如以下示例所示:

fn drone_does_not_fly_idle() {
    let drone = Drone::<Idle>::new();
    drone.move_to(10.0, 10.0); // comptime error: "move_to" is not a member of type Idle
}

error[E0599]: no method named `move_to` found for struct `drone::Drone<drone::Idle>` in the current scope
   --> src/drone.rs:128:15
    |
4   | / pub struct Drone<State>
5   | | where
6   | |     State: DroneState,
7   | | {
...   |
10  | |     state: PhantomData<State>,
11  | | }
    | |_- method `move_to` not found for this
...
128 |           drone.move_to(10.0, 10.0);
    |                 ^^^^^^^ method not found in `drone::Drone<drone::Idle>`

 

1
猜你喜欢