How to use trait fn argument in traits? the trait `x` cannot be made into an object

Hello,

I'm trying to port the PHP Symfony Console to Rust, but I have an issue that I don't understand when I add a trait function argument in a trait itself.

I know how to have trait function arguments in structs:

struct Application { ... }

impl Application {
    pub fn add<C: CommandInterface + 'static>(&mut self, command: C) { ... }
}

But when I do the same on a trait (OutputInterface) function itself I get an error:

trait OutputFormatterInterface {}

trait OutputInterface {
    fn set_formatter<F: OutputFormatterInterface + 'static>(&mut self, formatter: F);
}

struct ConsoleOutput {}

impl OutputInterface for ConsoleOutput {
    fn set_formatter<F: OutputFormatterInterface + 'static>(&mut self, formatter: F) { ... }
}

struct SymfonyStyle {
    output: Box<dyn OutputInterface>,
}

error[E0038]: the trait 'output::output_interface::OutputInterface' cannot be made into an object
--> src/style/symfony_style.rs:8:5
|
| output: Box<dyn OutputInterface>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait 'output::output_interface::OutputInterface' cannot be made into an object

What am I doing wrong here?

Kind regards,
Raymond

The way generic functions work in Rust is that for every choice of generic parameter, the compiler makes a separate version of that function in the assembly. Among other things this means that the compiler must know which generic parameters you actually use, because otherwise it doesn't know which versions to compile.

But this is exactly what happens when you make a trait object (e.g. a Box<dyn OutputInterface>). The compiler forgets what the underlying type is, so if you call a generic function on the trait object, it doesn't have enough information to know which versions of the generic function to generate.

To get around this, you can replace the generic function with a function that takes a trait object:

trait OutputInterface {
    fn set_formatter(&mut self, formatter: Box<dyn OutputFormatterInterface>);
}

This way, the compiler only needs to generate one version of set_formatter for each implementor of the trait, so it can be reused for every concrete OutputFormatterInterface.

Thanks Alice,

I figured this was working, but I didn't like the fact that now the caller needs to box the "interface" instance, while in other locations the caller doesn't. That makes it behave inconsistent. I originally had the approach you describe everywhere (and that worked), but that required more syntax everywhere (the boxing):

Application::new()
    .set_name("Console Application")
    .set_version("1.0.0")
    .add(Box::new(MyCommand::new()))
    .add(Box::new(TestCommand::new())
    .run(Box::new(ArgvInput::new()), Box::new(ConsoleOutput::new()));

Vs

Application::new()
    .set_name("Console Application")
    .set_version("1.0.0")
    .add(MyCommand::new())
    .add(TestCommand::new()
    .run(ArgvInput::new(), ConsoleOutput::new());

So I guess there is no escape from accepting boxes here?
What is your take on my consistency and less syntax vision?

Raymond

You can introduce a convenience method that hides the boxing like this:

trait OutputInterface {
    fn set_formatter_dyn(&mut self, formatter: Box<dyn OutputFormatterInterface>);
}

impl dyn OutputInterface {
    pub fn set_formatter<F: OutputFormatterInterface + 'static>(&mut self, formatter: F) {
        self.set_formatter_dyn(Box::new(formatter))
    }
}

This works because the compiler does not need to know the underlying type of OutputInterface to call methods implemented on the trait object dyn OutputInterface.

1 Like

Of course I will also recommend that you consider if you really need your Application object to be a trait? Rust isn't object oriented and the common OO pattern of putting all the methods on interfaces doesn't work very well here.

Thats genius, I'm face palming myself right now...

The convenience method can be used by the user, while the dyn interface method can be used by the framework itself.

Thanks!

1 Like

The Application itself is not a trait, but a struct, as there are not more variants on it.
The application receives one or more commands that implement the CommandInterface trait.
Then when running you need an input (argv, array, etc.) that implements InputInterface, and an output (null, console, stream, etc.) that implements OutputInterface.
The output itself can be formatted with a formatter that implements OutputFormatterInterface.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.