Object polymorphism/Moving Box<dyn T>

Hello.
I have a problem with polymorphism.
How to fix it?
Use Box ?

trait Converter
{
    fn static_common_func()
    {
        //..any code
    }
    
    fn process(&mut self);
}

struct ConverterA{}

impl Converter for ConverterA 
{
    fn process(&mut self){ println!("process for ConverterA");}
}

struct ConverterB{}

impl Converter for ConverterB 
{
    fn process(&mut self){ println!("process for ConverterB");}
}

Playground

If you try to use Box<dyn Converter> directly you will be told that Converter is not object safe because of static_common_func. When are you using static_common_func? Does it need to be called from the different implementers? If so, you could restrict it to only implementers that have a size, but then I believe that static function will get materialized for every implementer. Given that this is a static function, you might want to move it to a different trait or turn it into a function, but it all depends on what you want to accomplish and what tradeoffs you're ok with.

Playground:

trait Converter {
    fn static_common_func() where Self: Sized {
        //..any code
    }

    fn process(&mut self);
}

struct ConverterA {}

impl Converter for ConverterA {
    fn process(&mut self) {
        println!("process for ConverterA");
        <Self as Converter>::static_common_func();
    }
}

struct ConverterB {}

impl Converter for ConverterB {
    fn process(&mut self) {
        println!("process for ConverterB");
        Self::static_common_func();
    }
}

fn main() {
    let condition = "A";
    let converter: Box<dyn Converter>;

    if condition == "A" {
        converter = Box::new(ConverterA {});
    } else {
        converter = Box::new(ConverterB {});
    }
}

I want to move converter to tokio runtime.

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

let mut rt = Runtime::new().unwrap();

rt.block_on(async move {
        converter.process();
});

I'm guessing you're trying to have different functions, right? If you have everything in the same scope things work. Again, it depends on what you want to accomplish, but you can do something like Rust Playground.

I want to create two converter structures - which will process data in different ways.
But these structures have common functions - e.g. for processing arguments from clap lib.

If you have a fixed number of converters, have you considered making an enum?

Something like

enum AnyConverter {
    A(ConverterA),
    B(ConverterB),
}
impl Converter for AnyConverter {
    fn process(&mut self) {
        match self {
            AnyConverter::A(v) => v.process(),
            AnyConverter::B(v) => v.process(),
        }
    }
}

fn main() {
    let condition = "A";
    let converter;

    if condition == "A" {
        converter = AnyConverter::A(ConverterA {});
    } else {
        converter = AnyConverter::B(ConverterB {});
    }
}
1 Like

Thx for reply. But in your solution the Traits is not used (method overloading). Can it be done differently?

You should be able to move a Box<dyn Trait> into a spawned task. Can you post that version which didn't work?

use tokio::runtime::Runtime;
use async_trait::async_trait;

#[async_trait]
trait Converter {
    async fn process(&mut self);
}

struct ConverterA {}

#[async_trait]
impl Converter for ConverterA {
    async fn process(&mut self) {
        println!("process for ConverterA");
    }
}

struct ConverterB {}

#[async_trait]
impl Converter for ConverterB {
    async fn process(&mut self) {
        println!("process for ConverterB");
    }
}

fn main() {

    let condition = "A";
    let converter: Box<dyn Converter>;

    if condition == "A" {
        converter = Box::new(ConverterA {});
    } else {
        converter = Box::new(ConverterB {});
    }

    let mut rt = Runtime::new().unwrap();

    rt.block_on(async move {
            tokio::spawn(async move {
                converter.process().await;
            });
    });
}

Github

That doesn't compile at all due to several syntax errors. Can you show an example that gives just this error?

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

If the example has several syntax errors, you can't rely on type errors to be correct. The compiler might completely mis-interpret your code, and give unrelated errors.

   Compiling poly_test v0.1.0 (/home/mhanusek/work/VersionControl/poly_test)
error[E0277]: `dyn Converter` cannot be sent between threads safely
   --> src/main.rs:54:13
    |
54  |             tokio::spawn(async move {
    |             ^^^^^^^^^^^^ `dyn Converter` cannot be sent between threads safely
    | 
   ::: /home/mhanusek/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.21/src/task/spawn.rs:127:21
    |
127 |         T: Future + Send + 'static,
    |                     ---- required by this bound in `tokio::task::spawn::spawn`
    |
    = help: the trait `std::marker::Send` is not implemented for `dyn Converter`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<dyn Converter>`
    = note: required because it appears within the type `std::boxed::Box<dyn Converter>`
    = note: required because it appears within the type `[static generator@src/main.rs:54:37: 56:14 converter:std::boxed::Box<dyn Converter> _]`
    = note: required because it appears within the type `std::future::from_generator::GenFuture<[static generator@src/main.rs:54:37: 56:14 converter:std::boxed::Box<dyn Converter> _]>`
    = note: required because it appears within the type `impl std::future::Future`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

What code produced that error? I'm still seeing various syntax errors such as the missing semicolon on async fn process.

 rt.block_on(async move {
            tokio::spawn(async move {
                converter.process().await;
            });
    });

I updated the code.

When you wrote Box<dyn Converter>, you told the compiler to forget that the contents were Send, so it couldn't make use of that fact anymore. You can tell it that it should be Send with Box<dyn Converter + Send>.

playground

2 Likes

@alice thx for reply.
I have one more problem.
I'm trying to write a (static) function that will be common to different structures.

Code:

Error:

error[E0283]: type annotations needed
  --> src/main.rs:33:21
   |
6  |     fn common_function() -> String where Self: Sized
   |        ---------------                         ----- required by this bound in `Converter::common_function`
...
33 |     let condition = Converter::common_function();
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: cannot resolve `_: Converter`

You have to specify the actual type you are calling it on. You can't call it just on the trait.

ConverterA::common_function()
// or
ConverterB::common_function()

remember, implementors may override it

Thx for reply.
One more error.
I added a function:

async fn run(mut self)

to Converter trait.

error[E0161]: cannot move a value of type dyn Converter + std::marker::Send: the size of dyn Converter + std::marker::Send cannot be statically determined
    --> src/main.rs:1078:9
     |
1078 |         converter.run().await;
     |         ^^^^^^^^^

Try defining it in the impl block instead of the trait?

I not understand.

impl Converter
{
    async fn run(mut self)
    {
        // ... any code
        tokio::spawn(async move {
            self.process().await;
        });
    }
}

In this way?

In the impl Converter for ConverterA {