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):
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....
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.
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()
}
}