Rusty型別狀態Typestates入門 - rustype

banq發表於2021-12-05

如何使我們的計算機語言的型別系統更智慧,將型別推理從程式設計師轉移到編譯器?在當今,隨著系統變得越來越複雜,移動部件越來越多,能夠確保每個部件協同工作變得極為重要。

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>`

 

相關文章