I've been trying to fix this error for some time and I've come to the conclusion that it must be a concept error on my part, so please be patient and tell me where I'm making the wrong assumptions.
I want to write a pattern that I've implemented in lots of other languages: I have some objects with the same interface (in Rust, they implement the same trait) and want to add them to a collection to work with them dynamically. Here's the trait:
use nusb::Interface;
use std::io;
// Just in case, here's nusb::Interface:
// #[derive(Clone)]
// pub struct Interface {
// backend: Arc<platform::Interface>,
// }
pub struct Mouse {
iface: Interface,
}
pub trait Action {
fn help(&self) -> Vec<String>;
fn run(&self, mouse: Mouse, args: &[String]) -> io::Result<()>;
}
Then, I implement this trait in two modules. The first does nothing yet, but does implement the trait:
pub struct Help;
impl Action for Help {
fn help(&self) -> Vec<String> {
vec![]
}
fn run(&self, _: Mouse, _: &[String]) -> io::Result<()> {
Ok(())
}
}
I don't think the implementation details are relevant here, but just in case, here's the second struct's full implementation (Colour and Mouse elided):
use super::action::Action;
use crate::colour::Colour;
use crate::mouse::Mouse;
use std::io::{self, Error, ErrorKind};
const COLOUR_HINT: &str = "Elided...";
const HELP_HEAD: &str = r"Elided...";
const HELP_TAIL: &str = r#"Elided..."#;
pub struct Solid;
impl Action for Solid {
fn help(&self) -> Vec<String> {
let mut lines = vec![String::from(HELP_HEAD)];
Colour::available_colours()
.iter()
.for_each(|(name, colour)| lines.push(format!(" - {}: {}", name, colour)));
lines.push(String::from(HELP_TAIL));
lines
}
fn run(&self, mouse: Mouse, args: &[String]) -> io::Result<()> {
let Some(colour_name) = args.first() else {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("Expected colour name or value.\n{}", COLOUR_HINT),
));
};
let Some(colour) = Colour::from_str(colour_name) else {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("Unknown colour '{}'.\n{}", colour_name, COLOUR_HINT),
));
};
mouse.disable_onboard_memory()?;
mouse.set_solid_colour(&colour)
}
}
Now, I want to make a map with each of these structs so we can reference them by name. Again, this is a common pattern I'm well used to, so it may be here where I'm having the big concept error. Here's the map implementation:
pub struct Actions {
actions: HashMap<&'static str, Box<dyn Action>>,
}
impl Actions {
pub fn new() -> Self {
Self {
actions: HashMap::from_iter([
("help", Box::<dyn Action>::new(Help)),
("solid", Box::<dyn Action>::new(Solid))
]),
}
}
pub fn run(&self, name: &str, mouse: Mouse, args: &[String]) -> Option<io::Result<()>> {
match self.actions.get(name) {
Some(action) => Some(action.run(mouse, args)),
None => None,
}
}
}
As I understand, we cannot know the size of a dyn Action at compile time, so we have to store a ptr to it. To do so, we wrap the action object in a Box, same way it works in C++ by working with std::vector<std::unique_ptr<MyVirtualClass>>, for example. This should work automatically, but then I get this compiler error:
error[E0599]: the function or associated item `new` exists for struct `Box<dyn Action>`, but its trait bounds were not satisfied
--> src/actions.rs:21:45
|
21 | ("help", Box::<dyn Action>::new(Help)),
| ^^^ function or associated item cannot be called on `Box<dyn Action>` due to unsatisfied trait bounds
|
::: src/actions/action.rs:4:1
|
4 | pub trait Action {
| ---------------- doesn't satisfy `dyn Action: Sized`
|
note: if you're trying to build a new `Box<dyn Action>` consider using one of the following associated functions:
Box::<T>::from_raw
Box::<T>::from_non_null
Box::<T, A>::from_raw_in
Box::<T, A>::from_non_null_in
--> /home/groctel/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:1044:5
|
1044 | pub unsafe fn from_raw(raw: *mut T) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
1098 | pub unsafe fn from_non_null(ptr: NonNull<T>) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
1271 | pub unsafe fn from_raw_in(raw: *mut T, alloc: A) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
1324 | pub unsafe fn from_non_null_in(raw: NonNull<T>, alloc: A) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: the following trait bounds were not satisfied:
`dyn Action: Sized`
error[E0599]: the function or associated item `new` exists for struct `Box<dyn Action>`, but its trait bounds were not satisfied
--> src/actions.rs:22:46
|
22 | ("solid", Box::<dyn Action>::new(Solid))
| ^^^ function or associated item cannot be called on `Box<dyn Action>` due to unsatisfied trait bounds
|
::: src/actions/action.rs:4:1
|
4 | pub trait Action {
| ---------------- doesn't satisfy `dyn Action: Sized`
|
note: if you're trying to build a new `Box<dyn Action>` consider using one of the following associated functions:
Box::<T>::from_raw
Box::<T>::from_non_null
Box::<T, A>::from_raw_in
Box::<T, A>::from_non_null_in
--> /home/groctel/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:1044:5
|
1044 | pub unsafe fn from_raw(raw: *mut T) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
1098 | pub unsafe fn from_non_null(ptr: NonNull<T>) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
1271 | pub unsafe fn from_raw_in(raw: *mut T, alloc: A) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
1324 | pub unsafe fn from_non_null_in(raw: NonNull<T>, alloc: A) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: the following trait bounds were not satisfied:
`dyn Action: Sized`
For more information about this error, try `rustc --explain E0599`.
Reading about E0599 doesn't give me much information. After some digging, I found about E0038 which I read but it didn't help me understand why my particular code is failing. Then I went and read the Dyn compatibility section in the Rust Reference and for what I read, it seems that my trait should be dyn compatible? I don't find any of the incompatibilities listed in the reference applying to my trait.
I haven't tried the unsafe functions suggested by the compiler yet, as I am almost sure that this fairly simple code should be 100% safe... right?
So my questions are:
- Which of my assumptions of how traits work in Rust are wrong?
- How can I make this code compile and run, ideally without unsafe blocks?
- Is there a better, rustier way of building a structure like this?
Thanks ![]()