0

There are definitively other ways to solve this problem but I was just curious whether there was any way to make the following code work in Rust in some way I do not know yet.

The following code sample (playground)

use std::sync::{Arc, Mutex};

trait ProvidesFoo {
    fn magic(&mut self);
}

struct Foo {
    magic_value: u32
}

impl Default for Foo {
    fn default() -> Self {
        Self {magic_value: 42}
    }
}

impl ProvidesFoo for Foo {
    fn magic(&mut self) {
        println!("ProvidesFoo magic {}", self.magic_value);
    }
}

pub type SharedFooConcrete = Arc<Mutex<Box<Foo>>>;
pub type SharedFooTraitObj = Arc<Mutex<Box<dyn ProvidesFoo + Send + Sync>>>;

struct FooProvider {
    foo_as_trait_obj: SharedFooTraitObj

}

impl FooProvider {
    fn magic_and_then_some(&mut self) {
        let mut fooguard = self.foo_as_trait_obj.lock().unwrap();
        fooguard.magic();
        println!("Additional magic");
    }
}

fn uses_shared_foo_boxed_trait_obj(foo: SharedFooTraitObj) {
    let mut foo_provider = FooProvider {
        foo_as_trait_obj: foo
    };
    foo_provider.magic_and_then_some();
}
fn uses_shared_foo_concrete(foo: SharedFooConcrete) {
    let mut fooguard = foo.lock().unwrap();
    fooguard.magic();
}

fn main() {
    let shared_foo = Arc::new(Mutex::new(Box::new(Foo::default())));
    uses_shared_foo_concrete(shared_foo.clone());
    uses_shared_foo_boxed_trait_obj(shared_foo);
}

will fail to compile with the following error:

error[E0308]: mismatched types
  --> fsrc-example/src/bin/test2.rs:52:37
   |
52 |     uses_shared_foo_boxed_trait_obj(shared_foo);
   |     ------------------------------- ^^^^^^^^^^ expected trait object `dyn ProvidesFoo`, found struct `Foo`
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected struct `Arc<Mutex<Box<(dyn ProvidesFoo + Send + Sync + 'static)>>>`
              found struct `Arc<Mutex<Box<Foo>>>`
note: function defined here

There is definitely a way to "cast" a boxed trait object back to its concrete type like shown here, but this is basically the other way around. I come from a C++ background, so I am familiar with this type of API where a derived object can be passed as a base class.

The other possible solution I mentioned and used was to have a wrapper struct which has the SharedFooTraitObject as a field and implements the magic_and_then_some() operation on top. One can then pass that wrapper struct around and clone the Arc<Mutex>ed field for library code which expects only the trait object.

I was just curious whether this type of type coercion / casting is possible in Rust.

Kind Regards, RM

Spacefish
  • 305
  • 4
  • 11
  • Do you want to use the same `shared_foo` object for functions of both signatures? Or are you trying to simply create an object that you can pass into `uses_shared_foo_boxed_trait_obj`? – apilat Sep 10 '22 at 13:10
  • I want to use the same for functions of both signatures. My concrete use case during library programming was that I had an API which expected the concrete boxed object which called another API expecting the boxed trait object. I know that I can use explicit type annotations during object creation when only the trait object is required. – Spacefish Sep 10 '22 at 17:33
  • 1
    Do your APIs actually consume `Arc>`? In that unlikely case, I suspect what you're asking for is impossible since `Arc>>` and `Arc>>` are fundamentally different types. I assume your API actually expects one of `Box`, `Box`, `&Foo`, `&dyn ProvdesFoo` - in this case it is important exactly which one it is, as some conversions are possible but not all of them. If the API you are trying to use is public it would certainly be helpful to include it in the question. – apilat Sep 10 '22 at 21:33
  • My API uses a shared pool object so `Arc>` is actually consumed. It was in some demo application code where I wanted a helper function which stores specific kind of data in that shared pool object. This was the reason I required the concrete shared type. I am happy with the solution using regular composition now, it's how I solves most of the other problems related to trying to program like in C++ – Spacefish Sep 11 '22 at 11:44

1 Answers1

0

I'm not certain I understand correctly what you want; sorry if I'm wrong.

I removed all the wrapper types in order to focus on reference to concrete type vs reference to dyn trait.

Providing a reference to a concrete type where a reference to a dyn trait is expected is implicitly handled by the language because all the information is provided at compile time. A fat pointer pointing to the appropriate virtual table can be provided since the concrete type is well known.

On the other hand, when using a dyn trait, the compiler does not know anymore the original type, then providing a reference to a dyn trait where a reference to a concrete type is expected is impossible.

If we really want to achieve this, we need some runtime check. The Any trait can help for this purpose. When we downcast, we are certain of the concrete type, then we can obtain a reference to this concrete type.

Now, when it comes to Arc<Mutex<Box<Foo>>> vs Arc<Mutex<Box<dyn ProvidesFoo + Send + Sync>>>, no implicit or explicit conversion can happen because it is not only a matter of conversion between a fat pointer and a normal pointer: ownership and synchronisation come into play with the Mutex and Arc. We cannot clone the Arc and expect the new one contains a kind of pointer that is different from the original; if we could build another Arc with the expected kind of pointer inside, this second Arc would be totally independent of the original, thus this would be a totally different semantics concerning ownership.

I simply suggest you keep Arc<Mutex<Box<dyn ProvidesFoo + Send + Sync>>> all over the place if this is required by your API and, as soon as you need to get back to a concrete type, you obtain the dynamic reference it contains and use the solution with Any shown in this example.

use std::any::Any;

trait MyTrait: Any {
    fn show(&self);
    fn action(&mut self);
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

struct MyStructA {
    value: i32,
}

impl MyStructA {
    fn new(value: i32) -> Self {
        Self { value }
    }
}

impl MyTrait for MyStructA {
    fn show(&self) {
        println!("value is {}", self.value);
    }
    fn action(&mut self) {
        self.value += 1;
        self.show();
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

struct MyStructB {
    txt: String,
}

impl MyStructB {
    fn new(txt: String) -> Self {
        Self { txt }
    }
}

impl MyTrait for MyStructB {
    fn show(&self) {
        println!("txt is {}", self.txt);
    }
    fn action(&mut self) {
        self.txt.push_str(" •");
        self.show();
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

fn use_impl_trait(t: &mut impl MyTrait) {
    print!("impl: ");
    t.action();
}

fn use_dyn_trait(t: &mut dyn MyTrait) {
    print!("dyn: ");
    t.action();
}

fn main() {
    {
        println!("~~~~ using concrete types ~~~~");
        let mut msa = MyStructA::new(100);
        let msa_ref = &mut msa;
        use_dyn_trait(msa_ref);
        use_impl_trait(msa_ref);
        let mut msb = MyStructB::new("xxx".to_owned());
        let msb_ref = &mut msb;
        use_dyn_trait(msb_ref);
        use_impl_trait(msb_ref);
    }
    {
        println!("~~~~ using dynamic types ~~~~");
        let mut msa = MyStructA::new(200);
        let msa_dyn_ref: &mut dyn MyTrait = &mut msa;
        use_dyn_trait(msa_dyn_ref);
        if let Some(msa_ref) =
            msa_dyn_ref.as_any_mut().downcast_mut::<MyStructA>()
        {
            use_impl_trait(msa_ref);
        }
        let mut msb = MyStructB::new("yyy".to_owned());
        let msb_dyn_ref: &mut dyn MyTrait = &mut msb;
        use_dyn_trait(msb_dyn_ref);
        if let Some(msb_ref) =
            msb_dyn_ref.as_any_mut().downcast_mut::<MyStructB>()
        {
            use_impl_trait(msb_ref);
        }
    }
}
/*
~~~~ using concrete types ~~~~
dyn: value is 101
impl: value is 102
dyn: txt is xxx •
impl: txt is xxx • •
~~~~ using dynamic types ~~~~
dyn: value is 201
impl: value is 202
dyn: txt is yyy •
impl: txt is yyy • •
*/
prog-fh
  • 13,492
  • 1
  • 15
  • 30