何时应该使用结构体而不是枚举?

12

结构体和枚举类型相似。

在什么情况下使用结构体比使用枚举类型更好呢(反之亦然)?有没有人能给出一个清晰的例子,说明使用结构体优于使用枚举类型?


3
另一个小细节:pub enum 会使所有细节(变体和它们的字段)公开,而 pub struct 默认仍然保持字段私有。如果您需要在公共接口中公开 enum 数据但希望隐藏细节,则需要将(私有的)enum 包装在 pub struct 中。 - Stefan
3个回答

5
也许最简单的方式来解释基本区别是,枚举包含“变量”,你一次只能拥有一个,而结构体包含一个或多个字段,你必须拥有其中的所有字段。
因此,你可以使用enum来模拟类似错误代码的东西,你一次只能拥有一个:
enum ErrorCode {
    NoDataReceived,
    CorruptedData,
    BadResponse,
}

如果需要,枚举变量可以包含值。例如,我们可以像这样向 ErrorCode 添加一个 case:

enum ErrorCode {
    NoDataReceived,
    CorruptedData,
    BadResponse,
    BadHTTPCode(u16),
}

在这种情况下,ErrorCode::BadHTTPCode 的实例始终包含一个 u16
这使得每个单独的变体在行为上有点像元组结构体或单元结构体
// Unit structs have no fields
struct UnitStruct;

// Tuple structs contain anonymous values.
struct TupleStruct(u16, &'static str);

然而,将它们编写为枚举变体的优点在于,ErrorCode 的每个情况都可以存储在类型为 ErrorCode 的值中,如下所示(这对于不相关的结构体是不可能的)。
fn handle_error(error: ErrorCode) {
    match error {
        ErrorCode::NoDataReceived => println!("No data received."),
        ErrorCode::CorruptedData => println!("Data corrupted."),
        ErrorCode::BadResponse => println!("Bad response received from server."),
        ErrorCode::BadHTTPCode(code) => println!("Bad HTTP code received: {}", code)
    };
}

fn main() {
    handle_error(ErrorCode::NoDataReceived); // prints "No data received."
    handle_error(ErrorCode::BadHTTPCode(404)); // prints "Bad HTTP code received: 404"
}

您可以对枚举类型进行匹配,以确定您收到的是哪个变体,并根据其执行不同的操作。


相比之下,我上面没有提到的第三种结构体类型是最常用的 - 当他们简单地说“结构体”时,所有人都在指的就是这种类型的结构体。

struct Response {
    data: Option<Data>,
    response: HTTPResponse,
    error: String,
}

fn main() {
    let response = Response {
        data: Option::None,
        response: HTTPResponse::BadRequest,
        error: "Bad request".to_owned()
    }
}

请注意,在这种情况下,为了创建一个 Response,必须给出所有字段的值。
另外,response 的值是如何创建的(即 HTTPResponse::Something),这意味着 HTTPResponse 是一个枚举类型。它可能看起来像这样:
enum HTTPResponse {
    Ok,         // 200
    BadRequest, // 400
    NotFound,   // 404
}

结构体实现 trait 呢?比如说 trait HTTPResponse,以及 Ok、BadRequest 和 NotFound 这几个结构体。区别就在于实现 trait 的结构体有不同的方法实现,而枚举必须具有相同的方法实现吗? - Taztingo
如果我正确理解您的问题,那么答案是肯定的,这是其中一个重要区别:特质在每个结构体中的实现可能会有所不同,而对于整个枚举,您只需实现一次特质。但是,您始终可以在特质方法实现中匹配 self,并为每个变量创建有效地不同的实现。 - Søren Mortensen

2
枚举有多种可能性,结构体只能是一种可能的“类型”。在数学上,我们称结构体为乘积类型,枚举为乘积的总和。如果你只有一种可能性,使用结构体。例如,空间中的一个点总是三个数字。它永远不会是字符串、函数或其他什么东西。因此,应该是包含三个数字的结构体。另一方面,如果你正在构建一个数学表达式,它可以是(例如)一个数字或两个由运算符连接的表达式。它有多种可能性,所以应该用枚举。
简而言之,如果结构体可行,请使用结构体。Rust 可以围绕它进行优化,并且任何阅读您代码的用户都能更清楚地知道该值应该如何处理。

1
“Rust 可以优化它”是什么意思?当你到达代码生成阶段时,结构体实际上只是具有单个变量的枚举类型,因此它们在优化目的上应该是相同的。 - trent

0

Enum 是一种具有受限值集的类型。

enum Rainbow {
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}

let color = Red;

match color {
    Red => { handle Red case },
    // all the rest would go here
}

如果需要,您可以在枚举中存储数据。

enum ParseData {
    Whitespace,
    Token(String),
    Number(i32),
}

fn parse(input: String) -> Result<String, ParseData>;

结构体是表示事物的一种方式。

struct Window {
    title: String,
    position: Position,
    visible: boolean,
}

现在,您可以创建新的Window对象,代表屏幕上的一个窗口。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接