E0038 and E0599: Box<dyn Trait> does not satisfy std::marker::Sized

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 :slight_smile:

I am relatively certain that you should instead construct a Box<Solid> or Box<Help> and then coerce that into a Box<dyn Action>. You can’t directly use Box::<dyn Action>::new, because Box::<T>::new directly takes a T value that needs to be Sized. Thus the error you see.

…however, in Rust, this is possible but not the preferred option due to the overhead of boxing and dyn dispatch. Maybe something like the following would work?

enum ActionKind {
    Help(Help),
    Solid(Solid),
}

You could fallibly convert strings to ActionKind with the following:

impl<'a> TryFrom<&'a str> for ActionKind {
    type Error = ();

    // inline is entirely optional, I like to add it to small functions
    #[inline]
    fn from(value: &'a str) -> Result<Self, Self::Error> {
        match value {
            "help" => Ok(Self::Help),
            "solid" => Ok(Self::Solid),
            _ => Err(()),
        }
    }
}

Something like Actions::run could then convert name into the correct ActionKind and run the action.

2 Likes

I hadn't thought about coercing the box! That compiles and the makes code works as expected! (Tested with pretty mouse colours :stuck_out_tongue:). Here's how the code looks like now. Note an improvement in fn run, as clippy gave me a hint after the compiler error was gone:

impl Actions {
    pub fn new() -> Self {
        Self {
            actions: HashMap::from_iter([
                ("help", Box::<dyn Action>::from(Box::new(Help))),
                ("solid", Box::<dyn Action>::from(Box::new(Solid)))
            ]),
        }
    }

    pub fn run(&self, name: &str, mouse: Mouse, args: &[String]) -> Option<io::Result<()>> {
        self.actions.get(name).map(|action| action.run(mouse, args))
    }
}

I had considered using an enum but had switfly discarded it because they don't ensure a uniform interface. I've given it a spin and, while it's true that it doesn't have the oh so elegant one-liner DRY call, it actually reduces the code complexity a lot! Here's what the implementation has collapsed to after a couple of iterations:

pub struct Actions;

impl Actions {
    pub fn run(name: &str, mouse: Mouse, args: &[String]) -> Option<io::Result<()>> {
        match name {
            "help" => Some(Help.run(args)), // It turns out that Help doesn't use Mouse
            "solid" => Some(Solid.run(mouse, args)),
            _ => None,
        }
    }
}

Thank you very much for your help, both in helping me understand how to make y code work and for pointing me to a simpler solution. Have a great weekend :heart:

3 Likes

Not that they're necessary, but there are crates that give you back the DRY call for your enum and a trait you define, with no performance penalty, for example:

1 Like