Is there a simple way to mutate an enum field in Rust?

若如初见. 提交于 2020-03-16 06:02:13

问题


Suppose we have an enum that looks like this:

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

Now I created an instance of this enum of the subtype MyString and after some actions I want to mutate it. For example:

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);
        },
        _ => {}
    }
}

However, matching in such a way is pretty cumbersome when we know exactly from the context that my_enum can be only MyString. I would rather write something like this (not a correct Rust syntax):

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

And if, suppose, my_enum is of the subtype Field2, then to mutate x:

my_enum@Field2.x += 1.0;

Can I do something like this? I strongly suppose that the answer is "No", because if I remove _ => {} from the matches above, type checker starts complaining about non-exhaustive pattern matching:

patterns `Field1` and `Field2` not covered

though it can be inferred that my_enum can be nothing but MyString. By "inferred" I mean the compiler could track for all the variables of the type MyEnum what subtypes of values they can contain exactly.

I found a place in a larger code where this could be convenient, though I guess I can rewrite it in other way. However, I think that the compiler could be smarter and at least understand that in this context the pattern MyEnum::MyString is exhaustive. If the answer on the question above is really "No", as I suspect, I'm interested if this issue was discussed among Rust developers (maybe a RFCS link?) and if it is worth to make a feature request.


回答1:


As of Rust 1.15.1, the compiler won't recognize that a particular variable can only be a particular variant of an enum at some point of execution. As such, you always need to write an exhaustive match on it.

However, some Rust developers have been considering making it such that each enum variant would be its own type, which would be a subtype of the enum itself.

If your variant has many data fields, or if you have methods attached to it, you could consider wrapping the enum variant's fields in a struct and use that struct directly, bypassing the enum entirely until you do need the enum for whatever reason. If you have only a few fields, and don't need to call methods on the enum, then you might be able to get away with just keeping a mutable pointer to each field that you obtain at the beginning with an exhaustive match, like this:

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);
}

As of Rust 1.26, the explicit ref and ref mut keywords are no longer required.




回答2:


If you have an entire section of code in which the variable is known to have a particular type, you could just put that code inside the match, or if there's only one match arm which you care about, use 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);
    }
}

Alternatively, if it's just the verbose match (or if let) which is the problem, you can write methods to make it tidier:

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();
}

As of Rust 1.26, the explicit ref and ref mut keywords are no longer required.



来源:https://stackoverflow.com/questions/42462942/is-there-a-simple-way-to-mutate-an-enum-field-in-rust

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!