为什么 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

相关推荐
拉不动的猪5 分钟前
刷刷题31(vue实际项目问题)
前端·javascript·面试
zeijiershuai7 分钟前
Ajax-入门、axios请求方式、async、await、Vue生命周期
前端·javascript·ajax
恋猫de小郭9 分钟前
Flutter 小技巧之通过 MediaQuery 优化 App 性能
android·前端·flutter
只会写Bug的程序员18 分钟前
面试之《webpack从输入到输出经历了什么》
前端·面试·webpack
拉不动的猪20 分钟前
刷刷题30(vue3常规面试题)
前端·javascript·面试
夕颜11124 分钟前
排查问题的知识记录
后端
ZC·Shou24 分钟前
Rust 之一 基本环境搭建、各组件工具的文档、源码、配置
开发语言·rust·cargo·rustc·rustup·clippy·rustfmt
Hello.Reader26 分钟前
深入理解 Rust 中的模式匹配语法
开发语言·rust
zhuyasen29 分钟前
Go语言Viper配置详解:conf库优雅解析实战
后端·golang
狂炫一碗大米饭30 分钟前
面试小题:写一个函数实现将输入的数组按指定类型过滤
前端·javascript·面试