简体   繁体   中英

How can I easily get a reference to a value after it has been moved into a tuple-type enum variant?

I want to move a value into a tuple-type enum variant and obtain a reference to the value after it has been moved. I see how this is possible with an if let statement, but this seems like this should be unnecessary when the particular variant is known statically.

Is there any way to get the reference to the moved value without requiring an if let or match ?

This code block is a simple illustration of my question (see below for a more challenging case):

enum Transport {
    Car(u32),      // horsepower
    Horse(String), // name
}

fn do_something(x: &String) {
    println!(x);
}

fn main() {
    // Can I avoid needing this if, which is clearly redundant?
    if let Transport::Horse(ref name) = Transport::Horse("daisy".into()) {
        do_something(name);
    }
    else {
        // Can never happen
    }

    // I tried the following, it gives:
    //     "error[E0005]: refutable pattern in local binding: `Car(_)` not covered"
    let Transport::Horse(ref name) = Transport::Horse("daisy".into());
}

It is easy to find ways to side-step the issue in the above code, since there are no real interface requirements. Consider instead the following example, where I am building a simple API for building trees (where each node can have n children). Nodes have an add_child_node method returning a reference to the node that was added, to allow chaining of calls to quickly build deep trees. (It is debatable whether this is a good API, but that is irrelevant to the question). add_child_node must return a mutable reference to the contents of an enum variant. Is the if let required in this example (without changing the API)?

struct Node {
    children: Vec<Child>,
    // ...
}

enum Child {
    Node(Node),
    Leaf
}

impl Node {
    fn add_child_node(&mut self, node: Node) -> &mut Node {
        self.children.push(Child::Node(node));

        // It seems like this if should be unnecessary
        if let Some(&mut Child::Node(ref mut x)) = self.children.last() {
            return x;
        }

        // Required to compile, since we must return something
        unreachable!();
    }

    fn add_child_leaf(&mut self) {
        // ...
    }
}

No. You can use unreachable!() for the else case, and it's usually clear even without message/comment what's going on. The compiler is also very likely to optimize the check away.

If the variants have the same type you can implement AsRef and use the Transport as a &str :

enum Transport {
    Car(String),
    Horse(String),
}

fn do_something<S: AsRef<str>>(x: &S) {
    println!("{}", x.as_ref());
}

impl AsRef<str> for Transport {
    fn as_ref(&self) -> &str {
        match self {
            Transport::Car(s) => s,
            Transport::Horse(s) => s,
        }
    }
}

fn main() {
    let transport = Transport::Horse("daisy".into());
    do_something(&transport)
}

Playground

Otherwise you need to use a let if binding as you are doing. No need to use an else clause if you don't want to :

if let Transport::Horse(ref name) = Transport::Horse("daisy".into()) {
    do_something(name);
}

define From<Transport> for String :

…

impl From<Transport> for String {
    fn from(t: Transport) -> String {
        match t {
            Transport::Car(value)  => value.to_string(),
            Transport::Horse(name) => name,
        }
    }
}

fn do_something(x: Transport) {
    println!("{}", String::from(x));
}


fn main() {
    let horse = Transport::Horse("daisy".to_string());
    let car = Transport::Car(150);
    do_something(horse);
    do_something(car);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM