Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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):

[email protected]('!');
println!("{}", my_enum@MyString);

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

[email protected] += 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.

like image 998
Wolfram Avatar asked Feb 25 '17 23:02

Wolfram


People also ask

How do you convert enum to string in Rust?

The easiest way to convert an enum to a String in Rust is to implement the std::fmt::Display trait. Then you can call the to_string() method.

How do you match enums in Rust?

Adding data to enum variantsCreated a new instance of the enum and assigned it to a variable. Put that variable in a match statement. Destructured the contents of each enum variant into a variable within the match statement.

Can enums have methods Rust?

In Rust, methods cannot only be defined on structs, they can also be defined on tuples and enums, and even on built-in types like integers.

How do enums work in Rust?

An enum in Rust is a custom data type that represents data that can be anyone among several possible variants. Each variant in the enum can optionally have data associated with it. An enumerated type is defined using the enum keyword before the name of the enumeration.


2 Answers

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.

like image 195
Francis Gagné Avatar answered Nov 15 '22 10:11

Francis Gagné


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.

like image 36
Chris Emerson Avatar answered Nov 15 '22 09:11

Chris Emerson