【问题标题】:Is there a simple way to mutate an enum field in Rust?有没有一种简单的方法来改变 Rust 中的枚举字段?
【发布时间】:2017-07-16 17:16:52
【问题描述】:

假设我们有一个如下所示的枚举:

enum MyEnum {
    Field1,
    Field2 {x: f64, y: f64},
    /* Maybe some other fields */
    MyString(String),
}

现在我创建了这个枚举子类型 MyString 的实例,经过一些操作后,我想改变它。例如:

fn main() {
    let mut my_enum = MyEnum::MyString("Hello, world".to_string());
    /* Some actions */
    // Mutating the string
    match my_enum {
        MyEnum::MyString(ref mut content) => {
            content.push('!');
        },
        _ => {}
    }
    // Printing the string
    match my_enum {
        MyEnum::MyString(content) => {
            println!("{}", content);
        },
        _ => {}
    }
}

但是,当我们从上下文中确切地知道my_enum 可以 MyString 时,以这种方式进行匹配非常麻烦。我宁愿写这样的东西(不是正确的 Rust 语法):

my_enum@MyString.push('!');
println!("{}", my_enum@MyString);

如果,假设my_enumField2 的子类型,那么要变异x

my_enum@Field2.x += 1.0;

我可以这样做吗?我强烈认为答案是“否”,因为如果我从上面的匹配项中删除 _ => {},类型检查器就会开始抱怨非详尽的模式匹配:

patterns `Field1` and `Field2` not covered

尽管可以推断my_enum 只能是MyString。 “推断”是指编译器可以跟踪MyEnum 类型的所有变量,它们可以准确包含哪些值的子类型。

我在更大的代码中找到了一个地方,这可能很方便,但我想我可以用其他方式重写它。但是,我认为编译器可能会更聪明,并且至少可以理解在这种情况下,MyEnum::MyString 模式是详尽无遗的。如果上面问题的答案真的是“否”,我怀疑,如果这个问题在 Rust 开发人员之间讨论过(可能是一个 RFCS 链接?)以及是否值得提出功能请求,我很感兴趣。

【问题讨论】:

  • if let MyEnum::MyString(ref mut content) = my_enum { content.push('!'); }

标签: enums rust pattern-matching


【解决方案1】:

从 Rust 1.15.1 开始,编译器将无法识别特定变量在某个执行点只能是 enum 的特定变体。因此,您总是需要在上面写一个详尽的match

但是,一些 Rust 开发人员 have been considering 使得每个 enum 变体都是自己的类型,这将是 enum 本身的子类型。

如果您的变体有许多数据字段,或者您附加了方法,您可以考虑将enum 变体的字段包装在struct 中并直接使用struct,完全绕过枚举,直到您这样做无论出于何种原因都需要枚举。如果您只有几个字段,并且不需要调用枚举上的方法,那么您可能只需保留一个可变指针,该指针指向您在开始时获得的每个字段,并使用详尽的match,像这样:

fn main() {
    let mut my_enum = MyEnum::MyString("Hello, world".to_string());
    let content = 
        match my_enum {
            MyEnum::MyString(ref mut content) => content,
            _ => unreachable!(),
        };

    /* Some actions */
    // Mutating the string
    content.push('!');

    // Printing the string
    println!("{}", content);
}

Rust 1.26 开始,不再需要显式的refref mut 关键字。

【讨论】:

    【解决方案2】:

    如果您有一整段代码,其中已知变量具有特定类型,您可以将该代码放在 match 中,或者如果您只关心一个 match 分支,请使用if let:

    fn main() {
        let mut my_enum = MyEnum::MyString("Hello, world".to_string());
        /* Some actions */
        if let MyEnum::MyString(ref mut content) = my_enum {
            content.push('!');
            //...
            println!("{}", content);
        }
    }
    

    或者,如果只是冗长的match(或if let)是问题所在,您可以编写方法使其更整洁:

    impl MyEnum {
        fn push(&mut self, char c) {
            if let MyEnum::MyString(ref mut content) = *self {
                content.push(c);
            } else {
                unreachable!();
            }
        }
    
        // In practice print might be more generic, for example implement
        // Display
        fn print(&self) {
            if let MyEnum::MyString(ref content) = *self {
                println!("{}", content);
            }
        }
    }
    
    fn main() {
        //...
        my_enum.push('!');
        my_enum.print();
    }
    

    Rust 1.26 开始,不再需要显式的refref mut 关键字。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-15
      • 2010-09-17
      • 1970-01-01
      • 2022-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多