当前位置:首页 > 科技  > 软件

一起聊聊在Rust中使用枚举表示状态

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

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

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

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

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

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

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

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

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

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

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

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

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

u8的一个常见用例是与C代码交互,在这种情况下,使用u8似乎是唯一的选择。我们还可以将u8包装在一个新类型中!Gn128资讯网——每日最新资讯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,};

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

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

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

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

我们甚至还可以定义一个构造函数来验证输入:Gn128资讯网——每日最新资讯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"),        }    }}

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

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

使用枚举表示状态

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

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

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

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

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

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

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

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

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

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

我们还可以表示状态转换:Gn128资讯网——每日最新资讯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());    }}

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

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

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

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

使用枚举与C代码交互

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

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

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

你可以写一个Rust枚举来表示状态:Gn128资讯网——每日最新资讯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(()),        }    }}

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

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

现在,让我们用一个安全的Rust包装器包装C函数:Gn128资讯网——每日最新资讯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) })    }}

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

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

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

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

总结

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

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

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

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

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

标签:
  • 热门焦点
  • 6月iOS设备好评榜:第一蝉联榜首近一年

    6月iOS设备好评榜:第一蝉联榜首近一年

    作为安兔兔各种榜单里变化最小的那个,2023年6月的iOS好评榜和上个月相比没有任何排名上的变化,仅仅是部分设备好评率的下降,长年累月的用户评价和逐渐退出市场的老款机器让这
  • 线程通讯的三种方法!通俗易懂

    线程通讯的三种方法!通俗易懂

    线程通信是指多个线程之间通过某种机制进行协调和交互,例如,线程等待和通知机制就是线程通讯的主要手段之一。 在 Java 中,线程等待和通知的实现手段有以下几种方式:Object 类下
  • 服务存储设计模式:Cache-Aside模式

    服务存储设计模式:Cache-Aside模式

    Cache-Aside模式一种常用的缓存方式,通常是把数据从主存储加载到KV缓存中,加速后续的访问。在存在重复度的场景,Cache-Aside可以提升服务性能,降低底层存储的压力,缺点是缓存和底
  • Java NIO内存映射文件:提高文件读写效率的优秀实践!

    Java NIO内存映射文件:提高文件读写效率的优秀实践!

    Java的NIO库提供了内存映射文件的支持,它可以将文件映射到内存中,从而可以更快地读取和写入文件数据。本文将对Java内存映射文件进行详细的介绍和演示。内存映射文件概述内存
  • 三言两语说透柯里化和反柯里化

    三言两语说透柯里化和反柯里化

    JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是两种很有用的技术,可以帮助我们写出更加优雅、泛用的函数。本文将首先介绍柯里化和反柯里化的概念、实现原理和应用
  • 一篇文章带你了解 CSS 属性选择器

    一篇文章带你了解 CSS 属性选择器

    属性选择器对带有指定属性的 HTML 元素设置样式。可以为拥有指定属性的 HTML 元素设置样式,而不仅限于 class 和 id 属性。一、了解属性选择器CSS属性选择器提供了一种简单而
  • 拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    Tech星球(微信ID:tech618)文 | 陈桥辉 Tech星球独家获悉,拼多多在其APP内上线了&ldquo;本地生活&rdquo;入口,位置较深,位于首页的&ldquo;充值中心&rdquo;内,目前主要售卖美食相关的
  • iQOO 11S评测:行业唯一的200W标准版旗舰

    iQOO 11S评测:行业唯一的200W标准版旗舰

    【Techweb评测】去年底,iQOO推出了“电竞旗舰”iQOO 11系列,作为一款性能强机,该机不仅全球首发2K 144Hz E6全感屏,搭载了第二代骁龙8平台及144Hz电竞
  • iQOO Neo8 Pro评测:旗舰双芯加持 最强性能游戏旗舰

    iQOO Neo8 Pro评测:旗舰双芯加持 最强性能游戏旗舰

    【Techweb评测】去年10月,iQOO推出了一款Neo7手机,该机搭载了联发科天玑9000+,配备独显芯片Pro+,带来了同价位段最佳的游戏体验,一经上市便受到了诸多用
Top
Baidu
map