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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

使用枚举表示状态

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

使用枚举与C代码交互

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

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

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

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

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

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

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

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

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

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

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

总结

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

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

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

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

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

标签:
  • 热门焦点
  • Find N3入网:最高支持16+1TB

    OPPO将于近期登场的Find N3折叠屏目前已经正式入网,型号为PHN110。本次Find N3在外观方面相比前两代有很大的变化,不再是小号的横向折叠屏,而是跟别的厂商一样采用了较为常见的
  • 2023年Q2用户偏好榜:12+256G版本成新主流

    3月份的性能榜、性价比榜和好评榜之后,就要轮到2023年的第二季度偏好榜了,上半年的新机潮已经过去,最明显的肯定就是大内存和存储的机型了,另外部分中端机也取消了屏幕塑料支架
  • 不容错过的MSBuild技巧,必备用法详解和实践指南

    一、MSBuild简介MSBuild是一种基于XML的构建引擎,用于在.NET Framework和.NET Core应用程序中自动化构建过程。它是Visual Studio的构建引擎,可在命令行或其他构建工具中使用
  • 让我们一起聊聊文件的操作

    文件【1】文件是什么?文件是保存数据的地方,是数据源的一种,比如大家经常使用的word文档、txt文件、excel文件、jpg文件...都是文件。文件最主要的作用就是保存数据,它既可以保
  • 三分钟白话RocketMQ系列—— 如何发送消息

    我们知道RocketMQ主要分为消息 生产、存储(消息堆积)、消费 三大块领域。那接下来,我们白话一下,RocketMQ是如何发送消息的,揭秘消息生产全过程。注意,如果白话中不小心提到相关代
  • WebRTC.Net库开发进阶,教你实现屏幕共享和多路复用!

    WebRTC.Net库:让你的应用更亲民友好,实现视频通话无痛接入! 除了基本用法外,还有一些进阶用法可以更好地利用该库。自定义 STUN/TURN 服务器配置WebRTC.Net 默认使用 Google 的
  • 2天涨粉255万,又一赛道在抖音爆火

    来源:运营研究社作者 | 张知白编辑 | 杨佩汶设计 | 晏谈梦洁这个暑期,旅游赛道彻底火了:有的「地方」火了&mdash;&mdash;贵州村超旅游收入 1 个月超过 12 亿;有的「博主」火了&m
  • 机构称Q2国内智能手机销量同比下滑4% vivo份额重回第1

    7月29日消息,根据市场调查机构Counterpoint Research公布的最新报告,2023年第2季度中国智能手机销量同比下降4%,创新自2014年以来第2季度销量新低。报
  • OPPO K11搭载高性能石墨散热系统:旗舰同款 性能凉爽释放

    日前OPPO官方宣布,将于7月25日14:30举办新品发布会,届时全新的OPPO K11将正式与大家见面,将主打旗舰影像,和同档位竞品相比,其最大的卖点就是将配备索尼
Top