Size of dyn MyTrait cannot be statically determined in method which takes self?

Viewed 740

I'm creating a HashMap<u64, Box<dyn MyTrait>>. I can create the HashMap and insert a struct that implements MyTrait, but when I retrieve MyTrait and try to use it, the compiler complains at me:

error[E0161]: cannot move a value of type dyn MyTrait: the size of dyn MyTrait cannot be statically determined

I was under the impression that a trait consists of two pointers, one to the vtable and one to the object data. So the size of any trait, including MyTrait, should be 2 * pointer_size. Furthermore, the object data pointer points to a MyStruct, which is of known size. Clearly I'm wrong in my understanding but I can't figure out why.

Here's my code:

use std::collections::HashMap;

fn main() {
  let mut hm: HashMap<u64, Box<dyn MyTrait>> = HashMap::new();
  hm.insert(0, Box::new(MyStruct{num: 0}));
  match hm.get(&(0 as u64)) {
    Some(r) => {
      r.my_fun();
      }
    None => { println!("not found");}
  }
}

pub trait MyTrait {
  fn my_fun(self);
}

struct MyStruct {
  num: u64,
}

impl MyTrait for MyStruct {
  fn my_fun(self) {
    println!("num is {}", self.num);
    return
  }
}
1 Answers

When you declared the method in MyTrait as fn my_fun(self);, this creates a method that takes the self parameter by value, not by reference. In Rust, there is no implicit clone for most values (and for those that do, it only supports a straight-forward bit-per-bit copy).

In general, passing a parameter by value results in it being moved, and afterwards its old location is no longer valid and becomes invalid behavior to somehow access. (Although that this doesn't happen is enforced by the compiler.)

The error isn't saying that you can't have a Box<dyn MyTrait> in the value of a HashMap, but rather that you cannot move a dyn MyTrait out of the &Box<dyn MyTrait> reference you had gotten from the hm.get call. Even if you had a sized type there instead of an unsized one, this is still not possible because you cannot move a value out from behind a shared reference, unless the type is Copy. (In which case, it is, well, copied instead of moved at all.)

Most likely, you want to use fn my_fun(&self) instead, which takes the self parameter by reference instead of value.

If you want to mutate the value in the function, you should declare the function fn my_fun(&mut self) and replace the hm.get with hm.get_mut so you have a mutable reference to your MyStruct.

If you really need to take the self parameter by value instead of reference, you can write fn my_fun(self: Box<Self>) which declares that the self parameter is a boxed value, and then change the hm.get line to hm.remove. As the name implies, this leaves the value no longer accessible in the hash map.

Related