Troubles with Pin<Box<Any>>>

Hi, I have an object map that owns one boxed object per type, indexed by a tag. A simplified version of the code is this:

use std::any::Any;
use std::collections::HashMap;

trait Object {
    fn tag() -> u8;
}

struct ObjectMap {
    map: HashMap<u8, Box<dyn Any>>,
}

impl ObjectMap {
    fn new() -> ObjectMap {
        ObjectMap {
            map: HashMap::default(),
        }
    }

    fn register<OBJ: Object + 'static>(&mut self, o: Box<OBJ>) {
        self.map.insert(OBJ::tag(), o);
    }

    fn get_by_tag<OBJ: Object + 'static>(&self, id: u8) -> Option<&OBJ> {
        self.map.get(&id).map(|v| v.downcast_ref().unwrap())
    }
    fn get_mut_by_tag<OBJ: Object + 'static>(&mut self, id: u8) -> Option<&mut OBJ> {
        self.map.get_mut(&id).map(|v| v.downcast_mut().unwrap())
    }

    fn get<OBJ: Object + 'static>(&self) -> Option<&OBJ> {
        self.get_by_tag(OBJ::tag())
    }
    fn get_mut<OBJ: Object + 'static>(&mut self) -> Option<&mut OBJ> {
        self.get_mut_by_tag(OBJ::tag())
    }
}

struct Test1 {
    val: String,
}
struct Test2 {
    val: String,
}

impl Object for Test1 {
    fn tag() -> u8 {
        4
    }
}

impl Object for Test2 {
    fn tag() -> u8 {
        15
    }
}

fn main() {
    let test1 = Box::new(Test1 {
        val: "I am test1".into(),
    });
    let test2 = Box::new(Test2 {
        val: "I am test2".into(),
    });

    let mut objmap = ObjectMap::new();

    objmap.register(test1);
    objmap.register(test2);

    println!("{}", objmap.get::<Test1>().unwrap().val);
    println!("{}", objmap.get::<Test2>().unwrap().val);

    objmap.get_mut::<Test2>().unwrap().val = "Changed test2".into();
    
    println!("{}", objmap.get::<Test1>().unwrap().val);
    println!("{}", objmap.get::<Test2>().unwrap().val);
}

Output:

I am test1
I am test2
I am test1
Changed test2

(Playground)

I would like to change the object map to own pinned objects. Basically:

struct ObjectMap {
    map: HashMap<u8, Pin<Box<dyn Any>>>,
}

I would expect this to work transparently (with just small changes), but in fact I cannot make it to compile or work. I get this:

error[E0596]: cannot borrow data in a `&` reference as mutable
  --> src/main.rs:30:39
   |
30 |         self.map.get_mut(&id).map(|v| v.downcast_mut().unwrap())
   |                                       ^ cannot borrow as mutable

(Playground for the slightly modified version that uses Pin)

I think I found out that it doesn't work because dyn Any doesn't necessarily implement Unpin. I've also tried declaring the map as dyn Any + Unpin but at that point it seems that I can't call Any::downcast_ref or Any::downcast_mut anymore.

Can anybody shed some light on this?

I think you are right. I tried to "force" the compiler to give me a better error using the following line:

self.map.get_mut(&id).map(|v| Pin::get_mut(v.as_mut()).downcast_mut().unwrap()

In this case you get a much clearer error:

error[E0277]: the trait bound `(dyn std::any::Any + 'static): std::pin::Unpin` is not satisfied
  --> src/main.rs:30:39
   |
30 |         self.map.get_mut(&id).map(|v| Pin::get_mut(v.as_mut()).downcast_mut().unwrap())
   |                                       ^^^^^^^^^^^^ the trait `std::pin::Unpin` is not implemented for `(dyn std::any::Any + 'static)`
   |
   = note: required by `<std::pin::Pin<&'a mut T>>::get_mut`

I could be wrong because I tried just a few things with Pin, but this looks the actual reason for the error.

Playground

Ok but then I enter the followup problem. If I use dyn Any + Unpin:

struct ObjectMap {
    map: HashMap<u8, Pin<Box<dyn Any+Unpin>>>,
}

I can't access Any methods anymore:

error[E0599]: no method named `downcast_ref` found for type `&std::pin::Pin<std::boxed::Box<dyn std::any::Any + std::pin::Unpin>>` in the current scope
  --> src/main.rs:27:37
   |
27 |         self.map.get(&id).map(|v| v.downcast_ref().unwrap())
   |                                     ^^^^^^^^^^^^

error[E0599]: no method named `downcast_mut` found for type `&mut std::pin::Pin<std::boxed::Box<(dyn std::any::Any + std::pin::Unpin + 'static)>>` in the current scope
  --> src/main.rs:30:41
   |
30 |         self.map.get_mut(&id).map(|v| v.downcast_mut().unwrap())
   |                                         ^^^^^^^^^^^^

Playground

Ok, I am a bit confused as well: I used the Pin::get_mut + Pin::as_mut pattern, and I got this:

error[E0599]: no method named `downcast_mut` found for type `&mut (dyn std::any::Any + std::pin::Unpin + 'static)` in the current scope

But, theoretically, downcast_mut is implemented for dyn Any + 'static... Someone else has a better understanding of what is going on?

I believe the issue is dyn Any + Unpin + 'static isn’t considered a subtype of dyn Any + 'static, and would require the compiler to perform an “upcast”. There are several issues in Rust’s github repo talking about a similar thing with Any + Send (+ Sync). It seems the solution there was to add separate impl blocks for the combinations, and I suspect Unpin may also need such a thing (if Any + Unpin is expected to be somewhat common).

1 Like

I tried with a cast (v as &Any) / (v as &mut Any). It compiles, but surprisingly the calls to downcast_ref() / downcast_mut() fail and return None. I'm not sure why casting the trait object should make Any fail at runtime...

Playground

You want to coerce the interior value of the Pin, playground

4 Likes