1

I can use pattern matching on an enum that has one String parameter:

extern crate robots;

use std::any::Any;
use robots::actors::{Actor, ActorCell};

#[derive(Clone, PartialEq)]
pub enum ExampleMessage {
    Msg { param_a: String },
}

pub struct Dummy {}

impl Actor for Dummy {
    // Using `Any` is required for actors in RobotS
    fn receive(&self, message: Box<Any>, _context: ActorCell) {
        if let Ok(message) = Box::<Any>::downcast::<ExampleMessage>(message) {
            match *message {
                ExampleMessage::Msg { param_a } => println!("got message"),
            }
        }
    }
}

And yet I am unable to perform pattern matching on an enum with 2 parameters:

#[derive(Clone, PartialEq)]
pub enum ExampleMessage {
    Msg { param_a: String, param_b: usize },
}

impl Actor for Dummy {
    // Using `Any` is required for actors in RobotS
    fn receive(&self, message: Box<Any>, _context: ActorCell) {
        if let Ok(message) = Box::<Any>::downcast::<ExampleMessage>(message) {
            match *message {
                ExampleMessage::Msg { param_a, param_b } => println!("got message"),
            }
        }
    }
}

This results in the error:

error[E0382]: use of moved value: `message`
  --> src/example.rs:19:48
   |
19 |                 ExampleMessage::Msg { param_a, param_b } => {
   |                                       -------  ^^^^^^^ value used here after move
   |                                       |
   |                                       value moved here
   |
   = note: move occurs because `message.param_a` has type `std::string::String`, which does not implement the `Copy` trait

I tried pattern matching on the same enum without downcasting before, and this works fine but I am required to downcast. This just seems like very strange behavior to me and I don't know how to circumvent this error.

I am using Rust 1.19.0-nightly (afa1240e5 2017-04-29)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Anton
  • 1,314
  • 1
  • 12
  • 27

1 Answers1

4

I tried pattern matching on the same enum without downcasting before, and this works fine

This is a good attempt at reducing the problem. The issue is that you reduced too far. Downcasting a Box<T> to a Foo doesn't return a Foo, it returns a Box<Foo>:

fn downcast<T>(self) -> Result<Box<T>, Box<Any + 'static>> 

You can reproduce the problem with:

#[derive(Clone, PartialEq)]
pub enum ExampleMessage {
    Msg { param_a: String, param_b: usize },
}

fn receive2(message: Box<ExampleMessage>) {
    match *message {
        ExampleMessage::Msg { param_a, param_b } => println!("got message"),
    }
}

fn main() {}

The good news

This is a limitation of the current implementation of the borrow checker and your original code will work as-is when non-lexical lifetimes are enabled:

#![feature(nll)]

#[derive(Clone, PartialEq)]
pub enum ExampleMessage {
    Msg { param_a: String, param_b: usize },
}

fn receive2(message: Box<ExampleMessage>) {
    match *message {
        ExampleMessage::Msg { param_a, param_b } => println!("got message"),
    }
}

fn main() {}

The current reality

Non-lexical lifetimes and the MIR-based borrow checker are not yet stable!

When you match against a dereferenced value, the value is not normally moved. This allows you to do something like:

enum Foo {
    One,
    Two,
}

fn main() {
    let f = &Foo::One;
    match *f {
        Foo::One => {}
        Foo::Two => {}
    }
}

In this case, you wish to take ownership of the thing inside the Box1 in order to take ownership of the fields when destructuring it in the match. You can accomplish this by moving the value out of the box before trying to match on it.

The long way to do this is:

fn receive2(message: Box<ExampleMessage>) {
    let message = *message;
    match message {
        ExampleMessage::Msg { param_a, param_b } => println!("got message"),
    }
}

But you can also force the move by using curly braces:

fn receive2(message: Box<ExampleMessage>) {
    match {*message} {
        ExampleMessage::Msg { param_a, param_b } => println!("got message"),
    }
}

I don't fully understand why a single field would work; it's certainly inconsistent. My only guess is that the ownership of the Box is moved to the first param, the param is extracted, then the compiler tries to move it again to the next parameter.


1 — Moving the contained element out via * is a special power that only Box supports. For example, if you try to do this with a reference, you get the "cannot move out of borrowed content" error. You cannot implement the Deref trait to do this either; it's a hard-coded ability inside the compiler.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I'm having a very hard time fixing up my mental model here...what is wrong with your original version of `receive2`? And why does the problem disappear when binding only a single struct field? Does it have something to do with `Box<>` being special? Also -- what special power are you referring to? Thanks Shep – user1935361 May 02 '17 at 19:15
  • 1
    @user1935361 I added a little bit more text. – Shepmaster May 02 '17 at 19:28
  • in regards to [1] -- because `Deref` always returns a reference, I think I get it. – user1935361 May 02 '17 at 19:31