为什么 Rust 中的枚举(enum)很常见很好用? ? ?

为什么 Rust 中的枚举(enum)很常见很好用? ? ?

将 Rust 作为第二语言学习的人的一个常见的反应往往是,相比于其他任何语言, 枚举(enums) 在Rust中 得到了更好的支持 。粗略地浏览一下 Google 搜索"Rust 中的枚举"会在"人们也搜索过"中返回一个结果,询问"为什么 Rust 中的枚举如此好用"。乍一看,这似乎是一个好问题;孤立地讲,枚举是代表 有多个值的容器类型 - 例如:方向(东,南,西,北)或季节(春夏秋冬)。然而,Rust 以此为基础并以其他语言中根本不存在的方式增强枚举。

在本文中,我们将讨论 Rust 枚举明显优于其他语言的原因,以及它们的一些用例。

快速回顾enum

首先,为不熟悉的人快速回顾一下 Rust 枚举的实际含义:枚举是能够表示定义数量的变体的类型。考虑以下枚举:

rust 复制代码
enum Directions {
  Up,
  Down,
  Left,
  Right
}

这代表了一些方向。与使用字符串相比,使用枚举的优点是,当我们进行模式匹配时,我们可以简单地匹配不同的变体,而不必考虑字符串的变化。

其他语言中的 enum

对于某些上下文,让我们看一下枚举在其他语言中的样子。在 TypeScript 中,在 Google 上粗略搜索 Typescript 枚举将返回许多结果,这些结果要么告诉您以下内容:

  • 不要在 TypeScript 中使用枚举,因为它们很糟糕
  • 使用枚举只有一种正确的方法
  • 有许多使用枚举的错误方法,这些方法并不是立即显而易见的,因为枚举在编译为 JavaScript 时并不是一个东西

这告诉我们,虽然它们是 TypeScript 中的一项功能,但它们似乎并不是很受欢迎 - 通常是因为用户错误或语言怪癖导致使用枚举变得尴尬。

在Java和其他语言中,应该注意的是,枚举明显更加合理,因为它们没有编译到 底层语言 来支持------因此,必须在类中使用它们或使用方法重写之类的东西来做任何事情(就扩展或实现它们的功能而言)的本质意味着,作为一个整体,枚举并没有真正得到一流的支持。其他语言,比如Go,不一定有枚举,但是你可以用这样的方式来表示枚举(在Go中):

go 复制代码
const (
    A base = iota
    C
    T
    G
)

然而,缺少官方的 enum 关键字意味着使用起来似乎有些令人沮丧。

在 Rust 中,枚举通过作为 类似结构类型获得一流的支持 - 因此您可以拥有一个包含类似结构的结构的枚举,其中枚举变体 中存在 命名值,或者您可以拥有一个元组结构按数字引用变量,或者您可以只使用枚举变量本身。尽管除非您实例化它,否则您无法(默认情况下)在没有额外的crate的情况下声明初始值,但通过实现与枚举变体匹配的方法,然后返回您想要的任何内容,将枚举变体转换为另一种类型相对容易。只要你想这样做。

借助 ResultOption 类型,枚举在 Rust 类型系统中也得到了相当多的使用,这两种类型构成了 Rust 中错误处理系统的基础。您还可以通过实现枚举的traits来增强枚举,我们将在下面看到更多内容。

为 Enum 实现方法

Rust 中的枚举能够专门为枚举实现方法,无需类。我们来看看下面的方法:

rust 复制代码
enum Number {
  Odd(i64),
  Even(i64)
}

该枚举代表一个数字以及它是奇数还是偶数。我们可以为其实现一个方法,根据数字是否可以除以 2 自动实例化枚举变量,如下所示:

rust 复制代码
impl Number {
  fn from_i64(num: i64) => Self {
    match  num % 2 == 0 {
     true => Number::Even(num),
     false => Number::Odd(num)
    }
  }
}

这消除了许多样板代码,并使使用 Number::from_i64(number) 更容易使用该方法。在其他语言中,您当然可以编写一个返回枚举的单独方法,但是能够在枚举本身下对其进行命名空间使代码更加简洁。

就像 结构体(structs)一样,您也可以在枚举上使用派生宏;派生宏是 Rust 生态系统的重要组成部分,通过在编译时自动生成代码来简化样板代码生成。

Enum 作为错误类型

查看以下枚举:

rust 复制代码
#[derive(Debug)]
enum MyError {
  SQLError(sqlx::Error),
  RedisError(redis::RedisError),
  Forbidden,
  BadRequest,
  Unauthorized
}

此枚举表示 Web 应用程序可能失败的几种不同方式:例如,SQL 查询可能会因语法不正确而导致错误,您的 Redis 服务器可能在连接到它时出现错误,并且用户也可能尝试访问他们想要的页面。不应访问或填写错误的表格。

Error trait 要求我们的枚举类型同时实现 DebugDisplay - 我们已经为 Debug 特征使用了派生宏,因此我们不必手动实现它,但我们确实这样做了需要实现 Display 。我们可以通过匹配下面函数中的每个枚举变体来做到这一点:

rust 复制代码
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
          MyError::SQLError(e) => write!(f, format!("Something went wrong while using an SQL query: {e}")),
          MyError::RedisError(e) => write!(f, format!("Something went wrong while using Redis: {e}")),
          MyError::Forbidden => write!(f, "User tried to access a page but was forbidden!"),
          MyError::BadRequest => write!(f, "User tried to submit a HTTP request but it returned 400!"),
          MyError::Unauthorized => write!(f, "User tried to access a page but wasn't authorised!"),
        }
    }
}

实现此功能还可以免费为我们提供 .to_string() ,并且在完成后将根据枚举变体返回上述内容 - 这对我们很有用!

Error trait类型如下所示:

rust 复制代码
pub trait Error: Debug + Display {
    fn description(&self) -> &str { /* ... */ }
    fn cause(&self) -> Option<&Error> { /* ... */ }
    fn source(&self) -> Option<&(Error + 'static)> { /* ... */ }
}

但是,所有这些函数都是可选的,并且已经有默认实现 - 因此您可以简单地为您的类型实现 Error ,如下所示:

rust 复制代码
impl Error for MyError {}

从技术上讲,这将为您提供实现 - 当然,如果您想包含更多自定义行为(例如,包括特定枚举变体对保存变量的使用),您可能只想这样做。

当您使用像 Axum 或 Actix 这样的 Web 框架时,通常来说您不必自己实现 Error - 您将实现框架使用的任何类型,同时也实现 Error 。例如,在 Axum 中, IntoResponse 特征实现了 Error 并且也是一个成功的返回类型,因此从技术上讲,您可以将 Result<impl IntoResponse, impl IntoResponse> 作为函数返回签名。让我们看看您将如何实现它。

rust 复制代码
impl IntoResponse for MyError {
  fn into_response(&self) -> Response {
    match self {
        MyError::SQLError(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Error while using SQL: {e}")).into_response(),
        MyError::RedisError(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Error while using Redis: {e}")).into_response(),
        MyError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden!".to_string()).into_response(),
        MyError::BadRequest => (StatusCode::FORBIDDEN, "Bad request. Did you fill something out wrong?".to_string()).into_response(),
        MyError::Unauthorized => (StatusCode::FORBIDDEN, "Unauthorised!".to_string()).into_response(),
    }
  }
}

枚举作为错误类型非常有效:通过将错误类型设置为枚举,您只需要与枚举的每个分支进行匹配,而无需使用非详尽的模式标记( _ 替换您不想匹配的枚举变体,然后为其返回一些内容。

Enum 作为new-type("包装类型")

我们还可以将类型包装在枚举中,该枚举也可能有多个变体,其中包含来自单个crate或多个crate 的类型。与仅仅公开另一位所述 crate 的 API 相比,这样做的好处是,您可以为自己的程序引入新功能,同时通过不需要与原始类型本身交互来保持向后兼容性 - 您还可以使用它来创建抽象原始类型。例如, poise crate 构建在 serenity crate之上,通过将新类型公开为抽象来提供更高级的函数,而不是使用低级函数。

另一个例子:利用我们之前对 Display trait的了解,我们实际上可以在使用 .to_string() 时覆盖类型显示的内容!考虑一个包含密码和该结构创建时间的结构:

rust 复制代码
struct Password {
  password: String,
  created_at: DateTime<Utc>
}

我们可以用一个枚举来覆盖它:

rust 复制代码
enum PasswordEnum {
  Secured(Password),
  Unsecured(Password)
}

现在我们可以做两件事:

  • 可以将密码显示为一堆星星(基于长度)
  • 可以返回密码是否安全(根据某些标准)

See below for what this might look like:

请参阅下面的内容,了解它可能是什么样子:

rust 复制代码
impl fmt::Display for PasswordEnum {
      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
          PasswordEnum::Secured(password) => {
            password = password.chars().map(|_| "*".to_owned()).collect::<String>();
            write!(f, password);
          },
          PasswordEnum::Unsecured(password) => {
            password = password.chars().map(|_| "*".to_owned()).collect::<String>();
            write!(f, password);
          },
        }
    }
}

impl PasswordEnum {
  fn is_secure(&self) -> bool {
     match self {
       PasswordEnum::Secured(_) => true,
       PasswordEnum::Unsecured(_) => false
     }
  }
}

正如您所看到的,通过枚举使用new-type模式来发挥您的优势非常容易!您也可以使用结构来做到这一点。

尾声

感谢您的阅读,我希望您了解如何在 Rust 中使用枚举!枚举非常强大,是 Rust 开发的强大支柱的一部分。

有兴趣了解有关 Rust 的更多信息吗?这里有一些想法:


原文地址:Why Enums in Rust feel so much better

相关推荐
VinciYan12 分钟前
Rust使用Actix-web和SeaORM库开发WebAPI通过Swagger UI查看接口文档
rust·api·web·orm
吕彬-前端15 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架
青灯文案121 分钟前
SpringBoot 项目统一 API 响应结果封装示例
java·spring boot·后端
小白小白从不日白36 分钟前
react hooks--useCallback
前端·react.js·前端框架
恩婧44 分钟前
React项目中使用发布订阅模式
前端·react.js·前端框架·发布订阅模式
mez_Blog1 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
珊珊而川1 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试
森叶1 小时前
Electron 安装包 asar 解压定位问题实战
前端·javascript·electron
drebander1 小时前
ubuntu 安装 chrome 及 版本匹配的 chromedriver
前端·chrome
微尘81 小时前
C语言存储类型 auto,register,static,extern
服务器·c语言·开发语言·c++·后端