一起聊聊在Rust中使用枚举表示状态
2024-04-07 17:05:54 软件 156观看
摘要许多具有系统编程背景的Rust初学者倾向于使用bool(甚至u8—8位无符号整数类型)来表示“状态”。例如,如何使用bool来指示用户是否处于活动状态?struct User { // ... active: bool,}一开始,这可能看起来不错,但是随

许多具有系统编程背景的Rust初学者倾向于使用bool(甚至u8—8位无符号整数类型)来表示“状态”。ycn28资讯网——每日最新资讯28at.com

例如,如何使用bool来指示用户是否处于活动状态?ycn28资讯网——每日最新资讯28at.com

struct User {    // ...    active: bool,}

ycn28资讯网——每日最新资讯28at.com

一开始,这可能看起来不错,但是随着代码库的增长,会发现“active”不是二进制状态。用户可以处于许多不同的状态,用户可能被挂起或删除。但是,扩展User结构体可能会出现问题,因为代码的其他部分有可能依赖active是bool类型。ycn28资讯网——每日最新资讯28at.com

另一个问题是bool不是自文档化的。active = false是什么意思?用户是否处于非活动状态,或者用户被删除了,或者用户被挂起了?我们不知道!ycn28资讯网——每日最新资讯28at.com

或者,可以使用一个无符号整数来表示状态:ycn28资讯网——每日最新资讯28at.com

struct User {    // ...    status: u8,}

ycn28资讯网——每日最新资讯28at.com

这稍微好一点,因为我们现在可以使用不同的值来表示更多的状态:ycn28资讯网——每日最新资讯28at.com

const ACTIVE: u8 = 0;const INACTIVE: u8 = 1;const SUSPENDED: u8 = 2;const DELETED: u8 = 3;let user = User {    // ...    status: ACTIVE,};

ycn28资讯网——每日最新资讯28at.com

u8的一个常见用例是与C代码交互,在这种情况下,使用u8似乎是唯一的选择。我们还可以将u8包装在一个新类型中!ycn28资讯网——每日最新资讯28at.com

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,};

ycn28资讯网——每日最新资讯28at.com

这样我们就可以在UserStatus上定义方法:ycn28资讯网——每日最新资讯28at.com

impl UserStatus {    fn is_active(&self) -> bool {        self.0 == ACTIVE.0    }}

ycn28资讯网——每日最新资讯28at.com

我们甚至还可以定义一个构造函数来验证输入:ycn28资讯网——每日最新资讯28at.com

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"),        }    }}

ycn28资讯网——每日最新资讯28at.com

ycn28资讯网——每日最新资讯28at.com

使用枚举表示状态

ycn28资讯网——每日最新资讯28at.com

枚举是为域内的状态建模的好方法。它们以一种非常简洁的方式表达你的意图。ycn28资讯网——每日最新资讯28at.com

#[derive(Debug)]pub enum UserStatus {    /// 用户是活跃的,可以完全访问他们的帐户和任何相关功能。    Active,    /// 用户的帐户处于非活动状态。该状态可由用户或管理员恢复为激活状态。    Inactive,     /// 该用户的帐户已被暂时暂停,可能是由于可疑活动或违反政策。    /// 在此状态下,用户无法访问其帐户,并且可能需要管理员的干预才能恢复帐户。    Suspended,    /// 该用户的帐号已被永久删除,无法恢复。    /// 与该帐户关联的所有数据都可能被删除,用户需要创建一个新帐户才能再次使用该服务。    Deleted,}

ycn28资讯网——每日最新资讯28at.com

我们可以将这个枚举插入到User结构体中:ycn28资讯网——每日最新资讯28at.com

struct User {    // ...    status: UserStatus,}

ycn28资讯网——每日最新资讯28at.com

但这还不是全部。在Rust中,枚举比许多其他语言强大得多。例如,可以向枚举变量中添加数据:ycn28资讯网——每日最新资讯28at.com

#[derive(Debug)]pub enum UserStatus {    Active,    Inactive,    Suspended { until: DateTime<Utc> },    Deleted { deleted_at: DateTime<Utc> },}

ycn28资讯网——每日最新资讯28at.com

我们还可以表示状态转换:ycn28资讯网——每日最新资讯28at.com

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());    }}

ycn28资讯网——每日最新资讯28at.com

看看我们仅仅用几行代码就涵盖了多少内容!我们可以放心地扩展应用程序,因为我们知道不会意外地删除用户两次或重新激活已删除的用户。非法的状态转换现在是不可能的!ycn28资讯网——每日最新资讯28at.com

ycn28资讯网——每日最新资讯28at.com

ycn28资讯网——每日最新资讯28at.com

使用枚举与C代码交互

C代码:ycn28资讯网——每日最新资讯28at.com

typedef struct {    uint8_t status;} User;User *create_user(uint8_t status);

ycn28资讯网——每日最新资讯28at.com

你可以写一个Rust枚举来表示状态:ycn28资讯网——每日最新资讯28at.com

#[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(()),        }    }}

ycn28资讯网——每日最新资讯28at.com

注意到#[repr(u8)]属性了吗?它告诉编译器将此枚举表示为无符号8位整数。这对于与C代码的兼容性至关重要。ycn28资讯网——每日最新资讯28at.com

现在,让我们用一个安全的Rust包装器包装C函数:ycn28资讯网——每日最新资讯28at.com

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) })    }}

ycn28资讯网——每日最新资讯28at.com

Rust代码现在使用丰富的enum类型与C代码通信。ycn28资讯网——每日最新资讯28at.com

ycn28资讯网——每日最新资讯28at.com

ycn28资讯网——每日最新资讯28at.com

总结

Rust中的枚举比大多数其他语言更强大。它们可以用来优雅地表示状态转换——甚至可以跨越语言边界。ycn28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-81736-0.html一起聊聊在Rust中使用枚举表示状态

声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。

显示全文

上一篇:探索并发安全的Go语言Map - 深入理解Cmap

下一篇:你有思考过@Transactional事务是真的好用吗?

最新热点