0

I found this problem when working on a mid-size project. The following snippet is a minimal summary of the problem.

In the following code I try to map a list of enum variants into a Set of different enum variants. I use a closure so I can capture a mutable reference to my_list which is a list of source enum variants. The closure is then kept inside a MyType instance so it can be called later and the result used inside another method.

To keep the closure, I used a FnMut trait inside a Box. I also wrapped that inside an Option so I can set the closure after instance creation.

I based this a bit from the question asked here: structs with boxed vs. unboxed closures

use std::collections::HashSet;

enum Numbers {
    One,
    Two,
    Three,
}

#[derive(Eq, PartialEq, Hash)]
enum Romans {
    I,
    II,
    III,
}

struct MyType<'a> {
    func: Option<Box<dyn FnMut() -> HashSet<Romans> + 'a>>,
}

impl<'a> MyType<'a> {
    pub fn set_func<F>(&mut self, a_func: F)
        where F: FnMut() -> HashSet<Romans> + 'a {
        self.func = Some(Box::new(a_func));
    }

    pub fn run(&mut self) {
        let result = (self.func.unwrap())();
        if result.contains(&Romans::I) {
            println!("Roman one!");
        }
    }
}

fn main() {
    let my_list = vec![Numbers::One, Numbers::Three];
    let mut my_type = MyType {
        func: None,
    };
    my_type.set_func(|| -> HashSet<Romans> {
        HashSet::from(my_list
            .iter()
            .map(|item| {
                match item {
                    Numbers::One => Romans::I,
                    Numbers::Two => Romans::II,
                    Numbers::Three => Romans::III,
                }
            })
            .collect()
        )
    });

    my_type.run();
}

When I try to compile, I get the following error:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:27:23
   |
27 |         let result = (self.func.unwrap())();
   |                       ^^^^^^^^^ cannot move out of borrowed content

error: aborting due to previous error

I don't quite understand what is being moved out. Is it a hidden self? The resulting HashSet? or maybe the values inside the set? What am I doing wrong?

dospro
  • 450
  • 1
  • 5
  • 17

1 Answers1

1

The trouble you're having is that calling unwrap on an Option will consume it--it takes self as an argument. Inside run(), your MyType only has a &mut self reference to itself, so it cannot take ownership of its field.

The solution is to take mutable reference to the stored function instead:

    pub fn run(&mut self) {
        if let Some(func) = &mut self.func {
            let result = func();
            if result.contains(&Romans::I) {
                println!("Roman one!");
            }
        }
    }
Tom K
  • 430
  • 5
  • 22
  • Ha. So the problem was the `Option` part. After reading your answer, I looked for `Option` documentation. There already is a `as_mut` method: https://doc.rust-lang.org/std/option/enum.Option.html#method.as_mut . which simplifies the code a bit. – dospro Jun 02 '19 at 02:48