Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing boxed trait object to function accepting generic parameter implementing the trait

Tags:

rust

I have a function which returns a boxed trait object, and another function that accepts a reference to an object implementing the same trait. I would like to pass a reference to the boxed trait object to the second function, but I am unable to figure out how to do this.

Example simplified code:

trait MyTrait {
    fn foo(&self);
}

struct A {}

impl MyTrait for A {
    fn foo(&self) {
        println!("A");
    }
}

struct B {}

impl MyTrait for B{
    fn foo(&self) {
        println!("B");
    }
}

enum MyEnum {
    A,
    B,
}

fn create_object(my_enum: MyEnum) -> Box<dyn MyTrait> {
    let boxed_value: Box<dyn MyTrait> = match my_enum {
        MyEnum::A => Box::new(A{}),
        MyEnum::B => Box::new(B{}),
    };
    boxed_value
}

fn do_something<T: MyTrait>(obj: &T) {
    obj.foo();
}

fn main() {
    use std::borrow::BorrowMut;
    let boxed_value = create_object(MyEnum::A);
    do_something(boxed_value.borrow_mut());
}

The error I get:

error[E0282]: type annotations needed
  --> src\main.rs:42:5
   |
42 |     do_something(boxed_value.borrow_mut());
   |     ^^^^^^^^^^^^ ------------------------ this method call resolves to `&mut Borrowed`
   |     |
   |     cannot infer type for type parameter `T` declared on the function `do_something`  

Intuitively, I would have hoped that in this case Rust would use dynamic dispatch and wouldn't care about the concrete type T (similarly to what happens in C++ when you pass a reference to a base class), but this seems not to be the case.

How do I pass a reference to the boxed trait object (Box<dyn MyTrait>) to the second function (do_something)? Is this possible in some way? A solution requiring a change to do_something would also be acceptable.

like image 465
Genus Avatar asked Oct 24 '25 09:10

Genus


2 Answers

Intuitively, I would have hoped that in this case Rust would use dynamic dispatch and wouldn't care about the concrete type T (similarly to what happens in C++ when you pass a reference to a base class), but this seems not to be the case.

You can make that happen with a cast (or just type ascription, eventually) and by relaxing the default requirement for T to be Sized:

fn do_something<T: MyTrait + ?Sized>(obj: &T) {
    obj.foo();
}
use std::borrow::Borrow;
let boxed_value = create_object(MyEnum::A);
do_something(boxed_value.borrow() as &dyn MyTrait);

But if you’re not otherwise using T, you can opt into dynamic dispatch on the function side much more simply:

fn do_something(obj: &dyn Borrow) {
    obj.foo();
}
use std::borrow::Borrow;
let boxed_value = create_object(MyEnum::A);
do_something(boxed_value.borrow());

And if you don’t care that obj is a borrow and want to leave the option of static dispatch open, you can implement MyTrait for &dyn MyTrait:

impl MyTrait for &dyn MyTrait {
    fn foo(&self) {
        (*self).foo();
    }
}
fn do_something<T: MyTrait>(obj: T) {
    obj.foo();
}

// or, again, if not otherwise using T:

fn do_something(obj: impl MyTrait) {
    obj.foo();
}
use std::borrow::Borrow;
let boxed_value = create_object(MyEnum::A);
do_something(boxed_value.borrow());
like image 81
Ry- Avatar answered Oct 26 '25 05:10

Ry-


Instead of trying to unbox the value you can instead implement MyTrait on Box<dyn MyTrait> and forward to the boxed value.

impl MyTrait for Box<dyn MyTrait> {
    fn foo(&self) {
        self.deref().foo()
    }
}

Then you don't even need to call borrow_mut.

fn main() {
    use std::borrow::BorrowMut;
    let boxed_value = create_object(MyEnum::A);
    do_something(&boxed_value);
}

There's a working example in the playground

like image 38
Michael Anderson Avatar answered Oct 26 '25 05:10

Michael Anderson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!