Trait Object Auto Implementation That Passes Itself to a Function?

I've been trying all kinds of stuff, but I can't make this work in my program. Is there any way to have a trait object pass itself to a function that takes a dyn Trait object?

trait CliCommand {
    fn get_name(&self) -> &'static str;
    
    fn run(&self) {
        show_doc(self);
    }
}

fn show_doc(cmd: &dyn CliCommand) {
    dbg!(cmd.get_name());
}

struct MyCommand;

impl CliCommand for MyCommand {
    fn get_name(&self) -> &'static str {
        "my_command"
    }
}

fn main() {
    let cli = MyCommand;
    cli.run();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/main.rs:5:18
  |
5 |         show_doc(self);
  |                  ^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `Self`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = help: consider adding a `where Self: std::marker::Sized` bound
  = note: required for the cast to the object type `dyn CliCommand`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.


I understand that the object is not sized, but I can't figure out a way to convert the &self in the trait object implementation to a &dyn CliCommand or otherwise a Box<dyn CliCommand>.

Also it would be perfectly fine if the trait object could clone() itself before passing it to show_doc(), but that adds the Sized restriction which a trait object can't comply with.

1 Like

In this case, I believe you can't have a default implementation. Move the implementation of run to the impl of the concrete type: playground

Oh, bummer. The whole point in this case is so that you don't have to implement run() because it will be same for every command.

I guess I could move the show_doc() function to the trait as another default function implementation so that the show_doc() function would have the ability to call the functions on the trait with self.get_name().

Indeed, that works.

1 Like

If I have to do that I think that is a decent solution, but the show_doc() function in this case has a relatively large implementation and it would be nicer to be able to put it in its own file for organization's sake.

I feel like what I'm trying to do is sound, but it looks like there might not be any way to indicate to the compiler that I want to pass self as a trait object.

If it belongs to the object, I think it's better off in the trait impl anyway. But you can move it to another file by adding another method that converts your &self to a trait object, then passing this trait object to the separate show_doc() implementation. Now it's only the to_cli_command() (name is up to discussion) that needs to be implemented for every type, and it's trivial to implement, because it only needs to return self (then deref coercions will work their magic). It can even be automated using a macro.

I don't think I follow completely yet. How do you convert &self to a trait object?

trait CliCommand {
    fn to_cli_command(&self) -> &dyn CliCommand;
}

impl CliCommand for MyCommand {
    fn to_cli_command(&self) -> &dyn CliCommand {
        self
    }
}

(more)

Ah, interesting. I get it now. Only the concrete type knows how to cast itself to a &dyn CliCommand. That makes sense.

Then I could make a derive macro that literally just adds this to each implementation:

fn to_cli_command(&self) -> &dyn CliCommand {
    self
}

That, or, on nightly,

#![feature(specialization)]

default impl<T /* : Sized */> CliCommand for T {
    fn to_cli_command (self: &'_ Self) -> &'_ dyn CliCommand
    {
        self
    }
}

See this post for a more detailed answer: Explanation on fn(self: Box<Self>) for trait objects

1 Like

That's cool. That would be much nicer.

Is there a big reason not to use nightly features in my project? Is it mostly because of the possibility of the feature changing or disappearing in the future?

I'm building a CLI that will not be used as a library for other projects.

1 Like

That worked perfect! I think I'm just going to go ahead and use the nightly feature for now. If I run into trouble and I change it later.

If you don't want nightly, you can do this

The reason why you initial code didn't work is because a trait may be implemented by an unsized type, like [u8]. If so, then it can't be coerced to a trait object, which you require in show_doc

4 Likes

So when you do this:

impl<C: CliCommand> CliCommandExt for C {
    fn run(&self) {
        show_doc(self)
    }
}

Is impl<C: CliCommand + Sized> implied there? Which means that Self will be sized and can then be coerced?

You can just put a where Self: Sized bound on the run method.

trait CliCommand {
    fn get_name(&self) -> &'static str;
    fn run(&self) where Self: Sized {
        show_doc(self)
    }
}

Works fine, as far as I can tell.

I actually tried that earlier and it almost worked, but it doesn't satisfy calling run() on a Box<dyn CliCommand>:

fn main() {
    let cli = MyCommand;
    let cli_boxed = Box::new(cli) as Box<dyn CliCommand>;
    cli_boxed.run();
}

playground


@KrishnaSannasi's version does: playground.

I've got to test it on my actual codebase, but I really like that solution so far.

@KrishnaSannasi your solution worked great! Thanks! :tada: Thanks also @Yandros and @H2CO3. That helped me a lot.

1 Like

Yes, Sized is the default bound for everything except for trait declarations.

1 Like

If the CLI is just for you, or just to be used in short-to-midterm, then that's a great project to be using nightly on; else it can be risky to have a project that is not future-proof :wink:

1 Like