Search code examples
rusttraits

Using Trait Object for Struct - ERROR: wrong number of type arguments


I'm a total newbie to Rust, and I am facing with a problem using "Trait Object"...

For now, I have App struct with panels field which is a Vec of WavePanel struct which implements Panel trait.
Since I have more that implement Panel, I wanted panels to become more generic.
So, I tried specifying Panel as a Trait Object for the field, but it does not work....

This is how it currently looks (BEFORE the change):

https://github.com/minagawah/perlin-experiment/blob/6e8064140daedc0767733898621c951619ff4467/src_for_wasm/perlin-wave/src/app.rs#L14

use crate::panels::wave::WavePanel;
use crate::panels::Panel;

#[derive(Clone, Debug)]
pub struct App {
    points: Vec<Point>,
    points_prev: Vec<Point>,
    panels: Vec<WavePanel>, // <-- `WavePanel` struct works just fine.
}

impl App {
    pub fn new(config: &Config) -> Result<Self, String> {
        let color: String = config.color.clone();
        let color2: String = config.color2.clone();

        let mut panels = vec![];
        for panel in &config.panels {
            let id = panel.id.clone();
            match id.as_str() {
                "wave" => {
                    panels.push(WavePanel::new(
                        id.as_str(),
                        color.as_str(),
                        color2.as_str(),
                    )?);
                }
                _ => {}
            }
        }

        Ok(App {
            points: vec![],
            points_prev: vec![],
            panels: panels,
        })
    }
}

Here is the Panel trait:

pub trait Panel<G: Graphics> {
    fn g(&self) -> Rc<RefCell<G>>;

    fn reset(&mut self) {
        if let Ok(mut g) = self.g().try_borrow_mut() {
            let (width, height) = g.size();
            g.reset(width, height);
        };
    }
}

This is how WavePanel implements Panel trait:

#[derive(Clone, Debug)]
pub struct WavePanel {
    id: String,
    g: Rc<RefCell<WaveGraphics>>,
    graph_type: Rc<Cell<GraphType>>,
}

impl Panel<WaveGraphics> for WavePanel {
    fn g(&self) -> Rc<RefCell<WaveGraphics>> {
        self.g.clone()
    }
}

And, here is what I tried (AFTER):

use crate::panels::wave::WavePanel;
use crate::panels::Panel;

#[derive(Clone, Debug)]
pub struct App {
    points: Vec<Point>,
    points_prev: Vec<Point>,
    panels: Vec<Box<dyn Panel>>, // <-- This does not work...
}

impl App {
    pub fn new(config: &Config) -> Result<Self, String> {
        let color: String = config.color.clone();
        let color2: String = config.color2.clone();

        let mut panels = vec![];
        for panel in &config.panels {
            let id = panel.id.clone();
            match id.as_str() {
                "wave" => {
                    // Pushing it into Box this time.
                    panels.push(Box::new(
                        WavePanel::new(
                            id.as_str(),
                            color.as_str(),
                            color2.as_str(),
                        )?
                    ));
                }
                _ => {}
            }
        }

        Ok(App {
            points: vec![],
            points_prev: vec![],
            panels: panels,
        })
    }
}

Here is the error:

error[E0107]: wrong number of type arguments: expected 1, found 0
  --> src/app.rs:15:25
   |
15 |     panels: Vec<Box<dyn Panel>>,
   |                         ^^^^^ expected 1 type argument

error: aborting due to previous error

For more information about this error, try `rustc --explain E0107`.
error: could not compile `perlin-wave`

Shouldn't Box<dyn Panel> be explicit enough?
Tried so many things, and still no luck...
Please, I desperately need your help....

Edit: SOLVED (2021.3.4)

For the incorrect usage of the trait object, kmdreko was right. What I had for the trait was not Panel but Panel<G: Graphics>, and they are totally different.
As kmdreko mentioned, I stopped feeding G to Panel trait, and I replaced G with dyn Graphics (for the field g), and that nailed it!
It resolved the initial error.

However, there arose another problem...
When I actually push the struct WavePanel which derives Panel trait to panels: Vec<Box<dyn Panel>>, I got the error saying: expected trait object "dyn Panel", found struct "WavePanel". This is a problem about implementing a heterogeneous vec, and it is not kmdreko's fault. As far as the original question in concern, kmdreko's answer should be the correct answer.
For this, I found an explanation in here.
So, it was about Rust inferring WavePanel struct locally instead of dyn Panel trailt object. Right when I create Box::new(WavePanel::new(..)), now I explicitly tell that I want the trait object Vec<Box<dyn Panel>>, and it fixed it.

There was another problem....
Since it relates to the topic, I thought it is worth it to leave a note as well...
So, I have the field g in Panel now having dyn Graphics instead of G in Panel. A similar situation happened now to Graphics trait. Just like I had WavePanel struct for Panel trait, I have WaveGraphics struct for Graphics trait. In a way, Rust is now confused with the type for g field defined in Panel... For this issue, I decided to derive Any trait for Graphics trait. As long as I define as_any_mut() to always return Any struct in Graphics trait, when I actually want to use it, for instance, g.draw(), I can always convert the trait object into Any, and do this: g.as_any_mut().downcast_mut::<WaveGraphics>() .

For the final diff (for the all fix) is found here.


Solution

  • Shouldn't Box<dyn Panel> be explicit enough?

    No, you've defined Panel to be generic; Panel<A> and Panel<B> are different traits. If you want Panel to be used regardless of the graphics type, it can't be generic.

    If g() is only there to support reset() you can remove it:

    pub trait Panel {
        fn reset(&mut self);
    }
    
    impl Panel for WavePanel {
        fn reset(&mut self) {
            if let Ok(mut g) = self.g.try_borrow_mut() {
                let (width, height) = g.size();
                g.reset(width, height);
            };
        }
    }
    

    Or if g() is still necessary to get the graphics object generically, make it dynamic as well:

    pub trait Panel {
        fn g(&self) -> Rc<RefCell<dyn Graphics>>;
                               // ^^^^^^^^^^^^
    
        fn reset(&mut self) {
            if let Ok(mut g) = self.g().try_borrow_mut() {
                let (width, height) = g.size();
                g.reset(width, height);
            };
        }
    }
    
    impl Panel for WavePanel {
        fn g(&self) -> Rc<RefCell<dyn Graphics>> {
            self.g.clone()
        }
    }