一起聊聊在Rust中使用枚舉表示狀態(tài)
許多具有系統(tǒng)編程背景的Rust初學(xué)者傾向于使用bool(甚至u8—8位無符號(hào)整數(shù)類型)來表示“狀態(tài)”。
例如,如何使用bool來指示用戶是否處于活動(dòng)狀態(tài)?
struct User {
    // ...
    active: bool,
}一開始,這可能看起來不錯(cuò),但是隨著代碼庫的增長,會(huì)發(fā)現(xiàn)“active”不是二進(jìn)制狀態(tài)。用戶可以處于許多不同的狀態(tài),用戶可能被掛起或刪除。但是,擴(kuò)展User結(jié)構(gòu)體可能會(huì)出現(xiàn)問題,因?yàn)榇a的其他部分有可能依賴active是bool類型。
另一個(gè)問題是bool不是自文檔化的。active = false是什么意思?用戶是否處于非活動(dòng)狀態(tài),或者用戶被刪除了,或者用戶被掛起了?我們不知道!
或者,可以使用一個(gè)無符號(hào)整數(shù)來表示狀態(tài):
struct User {
    // ...
    status: u8,
}這稍微好一點(diǎn),因?yàn)槲覀儸F(xiàn)在可以使用不同的值來表示更多的狀態(tài):
const ACTIVE: u8 = 0;
const INACTIVE: u8 = 1;
const SUSPENDED: u8 = 2;
const DELETED: u8 = 3;
let user = User {
    // ...
    status: ACTIVE,
};u8的一個(gè)常見用例是與C代碼交互,在這種情況下,使用u8似乎是唯一的選擇。我們還可以將u8包裝在一個(gè)新類型中!
struct User {
    // ...
    status: UserStatus,
}
struct UserStatus(u8);
const ACTIVE: UserStatus = UserStatus(0);
const INACTIVE: UserStatus = UserStatus(1);
const SUSPENDED: UserStatus = UserStatus(2);
const DELETED: UserStatus = UserStatus(3);
let user = User {
    // ...
    status: ACTIVE,
};這樣我們就可以在UserStatus上定義方法:
impl UserStatus {
    fn is_active(&self) -> bool {
        self.0 == ACTIVE.0
    }
}我們甚至還可以定義一個(gè)構(gòu)造函數(shù)來驗(yàn)證輸入:
impl UserStatus {
    fn new(status: u8) -> Result<Self, &'static str> {
        match status {
            ACTIVE.0 => Ok(ACTIVE),
            INACTIVE.0 => Ok(INACTIVE),
            SUSPENDED.0 => Ok(SUSPENDED),
            DELETED.0 => Ok(DELETED),
            _ => Err("Invalid status"),
        }
    }
}使用枚舉表示狀態(tài)
枚舉是為域內(nèi)的狀態(tài)建模的好方法。它們以一種非常簡潔的方式表達(dá)你的意圖。
#[derive(Debug)]
pub enum UserStatus {
    /// 用戶是活躍的,可以完全訪問他們的帳戶和任何相關(guān)功能。
    Active,
    /// 用戶的帳戶處于非活動(dòng)狀態(tài)。該狀態(tài)可由用戶或管理員恢復(fù)為激活狀態(tài)。
    Inactive,
     /// 該用戶的帳戶已被暫時(shí)暫停,可能是由于可疑活動(dòng)或違反政策。
    /// 在此狀態(tài)下,用戶無法訪問其帳戶,并且可能需要管理員的干預(yù)才能恢復(fù)帳戶。
    Suspended,
    /// 該用戶的帳號(hào)已被永久刪除,無法恢復(fù)。
    /// 與該帳戶關(guān)聯(lián)的所有數(shù)據(jù)都可能被刪除,用戶需要?jiǎng)?chuàng)建一個(gè)新帳戶才能再次使用該服務(wù)。
    Deleted,
}我們可以將這個(gè)枚舉插入到User結(jié)構(gòu)體中:
struct User {
    // ...
    status: UserStatus,
}但這還不是全部。在Rust中,枚舉比許多其他語言強(qiáng)大得多。例如,可以向枚舉變量中添加數(shù)據(jù):
#[derive(Debug)]
pub enum UserStatus {
    Active,
    Inactive,
    Suspended { until: DateTime<Utc> },
    Deleted { deleted_at: DateTime<Utc> },
}我們還可以表示狀態(tài)轉(zhuǎn)換:
use chrono::{DateTime, Utc};
#[derive(Debug)]
pub enum UserStatus {
    Active,
    Inactive,
    Suspended { until: DateTime<Utc> },
    Deleted { deleted_at: DateTime<Utc> },
}
impl UserStatus {
    /// 暫停用戶直到指定日期
    fn suspend(&mut self, until: DateTime<Utc>) {
        match self {
            UserStatus::Active => *self = UserStatus::Suspended { until },
            _ => {}
        }
    }
    /// 激活用戶
    fn activate(&mut self) -> Result<(), &'static str> {
        match self {
            // A deleted user can't be activated!
            UserStatus::Deleted { .. } => return Err("can't activate a deleted user"),
            _ => *self = UserStatus::Active
        }
        Ok(())
    }
    /// 刪除用戶,這是一個(gè)永久的動(dòng)作!
    fn delete(&mut self) {
        if let UserStatus::Deleted { .. } = self {
            // 已經(jīng)刪除,不要再設(shè)置deleted_at字段。
            return;
        }
        *self = UserStatus::Deleted {
            deleted_at: Utc::now(),
        }
    }
    fn is_active(&self) -> bool {
        matches!(self, UserStatus::Active)
    }
    fn is_suspended(&self) -> bool {
        matches!(self, UserStatus::Suspended { .. })
    }
    fn is_deleted(&self) -> bool {
        matches!(self, UserStatus::Deleted { .. })
    }
}
#[cfg(test)]
mod tests {
    use chrono::Duration;
    use super::*;
    #[test]
    fn test_user_status() -> Result<(), &'static str>{
        let mut status = UserStatus::Active;
        assert!(status.is_active());
        // 暫停到明天
        status.suspend(Utc::now() + Duration::days(1));
        assert!(status.is_suspended());
        status.activate()?;
        assert!(status.is_active());
        status.delete();
        assert!(status.is_deleted());
        Ok(())
    }
    #[test]
    fn test_user_status_transition() {
        let mut status = UserStatus::Active;
        assert!(status.is_active());
        status.delete();
        assert!(status.is_deleted());
        // 無法激活已刪除的用戶
        assert!(status.activate().is_err());
    }
}看看我們僅僅用幾行代碼就涵蓋了多少內(nèi)容!我們可以放心地?cái)U(kuò)展應(yīng)用程序,因?yàn)槲覀冎啦粫?huì)意外地刪除用戶兩次或重新激活已刪除的用戶。非法的狀態(tài)轉(zhuǎn)換現(xiàn)在是不可能的!
使用枚舉與C代碼交互
C代碼:
typedef struct {
    uint8_t status;
} User;
User *create_user(uint8_t status);你可以寫一個(gè)Rust枚舉來表示狀態(tài):
#[repr(u8)]
#[derive(Debug, PartialEq)]
pub enum UserStatus {
    Active = 0,
    Inactive,
    Suspended,
    Deleted,
}
impl TryFrom<u8> for UserStatus {
    type Error = ();
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(UserStatus::Active),
            1 => Ok(UserStatus::Inactive),
            2 => Ok(UserStatus::Suspended),
            3 => Ok(UserStatus::Deleted),
            _ => Err(()),
        }
    }
}注意到#[repr(u8)]屬性了嗎?它告訴編譯器將此枚舉表示為無符號(hào)8位整數(shù)。這對(duì)于與C代碼的兼容性至關(guān)重要。
現(xiàn)在,讓我們用一個(gè)安全的Rust包裝器包裝C函數(shù):
extern "C" {
    fn create_user(status: u8) -> *mut User;
}
pub fn create_user_wrapper(status: UserStatus) -> Result<User, &'static str> {
    let user = unsafe { create_user(status as u8) };
    if user.is_null() {
        Err("Failed to create user")
    } else {
        Ok(unsafe { *Box::from_raw(user) })
    }
}Rust代碼現(xiàn)在使用豐富的enum類型與C代碼通信。
總結(jié)
Rust中的枚舉比大多數(shù)其他語言更強(qiáng)大。它們可以用來優(yōu)雅地表示狀態(tài)轉(zhuǎn)換——甚至可以跨越語言邊界。















 
 
 
















 
 
 
 