許多具有系統編程背景的Rust初學者傾向于使用bool(甚至u8—8位無符號整數類型)來表示“狀態”。
例如,如何使用bool來指示用戶是否處于活動狀態?
struct User { // ... active: bool,}
一開始,這可能看起來不錯,但是隨著代碼庫的增長,會發現“active”不是二進制狀態。用戶可以處于許多不同的狀態,用戶可能被掛起或刪除。但是,擴展User結構體可能會出現問題,因為代碼的其他部分有可能依賴active是bool類型。
另一個問題是bool不是自文檔化的。active = false是什么意思?用戶是否處于非活動狀態,或者用戶被刪除了,或者用戶被掛起了?我們不知道!
或者,可以使用一個無符號整數來表示狀態:
struct User { // ... status: u8,}
這稍微好一點,因為我們現在可以使用不同的值來表示更多的狀態:
const ACTIVE: u8 = 0;const INACTIVE: u8 = 1;const SUSPENDED: u8 = 2;const DELETED: u8 = 3;let user = User { // ... status: ACTIVE,};
u8的一個常見用例是與C代碼交互,在這種情況下,使用u8似乎是唯一的選擇。我們還可以將u8包裝在一個新類型中!
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 }}
我們甚至還可以定義一個構造函數來驗證輸入:
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"), } }}
枚舉是為域內的狀態建模的好方法。它們以一種非常簡潔的方式表達你的意圖。
#[derive(Debug)]pub enum UserStatus { /// 用戶是活躍的,可以完全訪問他們的帳戶和任何相關功能。 Active, /// 用戶的帳戶處于非活動狀態。該狀態可由用戶或管理員恢復為激活狀態。 Inactive, /// 該用戶的帳戶已被暫時暫停,可能是由于可疑活動或違反政策。 /// 在此狀態下,用戶無法訪問其帳戶,并且可能需要管理員的干預才能恢復帳戶。 Suspended, /// 該用戶的帳號已被永久刪除,無法恢復。 /// 與該帳戶關聯的所有數據都可能被刪除,用戶需要創建一個新帳戶才能再次使用該服務。 Deleted,}
我們可以將這個枚舉插入到User結構體中:
struct User { // ... status: UserStatus,}
但這還不是全部。在Rust中,枚舉比許多其他語言強大得多。例如,可以向枚舉變量中添加數據:
#[derive(Debug)]pub enum UserStatus { Active, Inactive, Suspended { until: DateTime<Utc> }, Deleted { deleted_at: DateTime<Utc> },}
我們還可以表示狀態轉換:
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(()) } /// 刪除用戶,這是一個永久的動作! fn delete(&mut self) { if let UserStatus::Deleted { .. } = self { // 已經刪除,不要再設置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()); }}
看看我們僅僅用幾行代碼就涵蓋了多少內容!我們可以放心地擴展應用程序,因為我們知道不會意外地刪除用戶兩次或重新激活已刪除的用戶。非法的狀態轉換現在是不可能的!
C代碼:
typedef struct { uint8_t status;} User;User *create_user(uint8_t status);
你可以寫一個Rust枚舉來表示狀態:
#[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)]屬性了嗎?它告訴編譯器將此枚舉表示為無符號8位整數。這對于與C代碼的兼容性至關重要。
現在,讓我們用一個安全的Rust包裝器包裝C函數:
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代碼現在使用豐富的enum類型與C代碼通信。
Rust中的枚舉比大多數其他語言更強大。它們可以用來優雅地表示狀態轉換——甚至可以跨越語言邊界。
本文鏈接:http://www.www897cc.com/showinfo-26-81736-0.html一起聊聊在Rust中使用枚舉表示狀態
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com