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