Case scenario
Have you ever done something similar?
trait Greet {
fn greet(&self) -> String;
}
struct Alice;
impl Greet for Alice {
fn greet(&self) -> String {
"Hello".to_string()
}
}
struct Carlo;
impl Greet for Carlo {
fn greet(&self) -> String {
"Ciao".to_string()
}
}
// ...
struct User {
greet: Greet
}
// ...
let greet = Alice;
let user = User { greet };
This is something everyone has tried to do in Rust: dynamic types inside of data structures. There are two different ways to achieve this in Rust:
- Box dyn
- Generics
But have you ever wondered what's the best option? In this article I'm going to try to make you understand both what's actually better, and how to deal with conditional typing.
How is this done
We've just seen this in the introduction example:
struct User {
greet: Greet // <-- Greet is a trait here
}
Well, of course we can't put Greet in our struct this way, indeed we'll have this error:
error[E0782]: expected a type, found a trait
--> src/main.rs:26:3
|
26 | greet: Greet
| ^^^^^
|
help: you can add the `dyn` keyword if you want a trait object
|
26 | greet: dyn Greet
| +++
So we have two different approaches we can take here:
Box Dyn
We can just wrap our Greet into a Box like this
struct User {
greet: Box<dyn Greet>,
}
let greet = Alice;
let user = User { greet: Box::new(greet) };

Here someone could say that this might not be the best approach though and there is a better way to do the same thing.
Did you mean Generics?
Generics
In place of using a Box dyn we could use Generics, which results in a smarter implementation:
struct User<T>
where T: Greet
{
greet: T,
}
let greet = Alice;
let user = User { greet };
It looks fine, but does this create some issues? What about conditional typing?
Conditional types with generics
But what if we had to instantiate User with a different Greet implementation on a certain condition? Let's take this code for example
let user = match name {
"carlo" => User { greet: Carlo },
"alice" => User { greet: Alice },
_ => panic!("Unknown user"),
};
This would result in an error, because the type of user can't be determined
error[E0308]: `match` arms have incompatible types
--> src/main.rs:36:20
|
34 | let user = match name {
| ________________-
35 | | "carlo" => User { greet: Carlo },
| | --------------------- this is found to be of type `User<Carlo>`
36 | | "alice" => User { greet: Alice },
| | ^^^^^^^^^^^^^^^^^^^^^ expected `User<Carlo>`, found `User<Alice>`
37 | | };
| |_____- `match` arms have incompatible types
|
= note: expected struct `User<Carlo>`
found struct `User<Alice>`
Indeed we can't have in the same variable User<Carlo> and User<Alice>.
While if we used Box dyn this wouldn't be an issue:
let user = match name {
"carlo" => User { greet: Box::new(Carlo) },
"alice" => User { greet: Box::new(Alice) },
_ => panic!("Unknown user"),
};
So apparently, in this case the only way we can achieve this is by using Box dyn. But, wait, is it true?
Wrap generics
Actually there's a different way to get the same result, by using an enum that wraps our Greet types and implements Greet itself, like this:
enum MyGreet {
Alice(Alice),
Carlo(Carlo),
}
impl MyGreet {
/// Call the given closure with the appropriate [`Greet`] implementation
fn on_greet<F, T>(&self, f: F) -> T
where
F: FnOnce(&dyn Greet) -> T,
{
match self {
Self::Alice(v) => f(v),
Self::Carlo(v) => f(v),
}
}
}
impl Greet for MyGreet {
fn greet(&self) -> String {
self.on_greet(|greet| greet.greet())
}
}
let user = match name {
"carlo" => User { greet: MyGreet::Carlo(Carlo) },
"alice" => User { greet: MyGreet::Alice(Alice) },
_ => panic!("Unknown user"),
};
And this builds. So two different ways to achieve the same thing? Yeah, more or less, but there are actually some important differences in these two approaches. Let's dive in!
Box dyn Vs. Generics Wrapper - What's better?
Before starting the analysis, I want you to imagine that this type containing the Dynamic type
is exposed publicly in a library you're using. So, what would be better for you?
Flexibility
Generics and generics wrappers allow you to have more custom code, on the other hand generics wrappers may be complex to achieve in case where some bounds must be respected by the inner type, and in some cases we may have several generics to include inside of our Dynamic type data, or could even be undetermined. In all of these cases Box dyn should be preferred.
Performance
Talking about performance, both cases have pros and cons:
- Generics doesn't have an overhead for dispatching (but generic wrappers do have an overhead though!), on the other hand the compiler must generate specific-code for each type, so the binary size will be bigger.
- Box dyn has a dispatching overhead, but the binary size will be much smaller.
But, eventually we could say that Box dyn is the winner if we talk about performance.
Library APIs
A topic we can't ignore is Library APIs and in particular Open Source libraries.
When we implement a library and we have to expose a type like this
struct Data {
imp: MyTrait
}
what is better for the library users? In this case, Generics win for sure.
Generics give library users better performance if they don't use wrappers and a few generic types (as usually happens), and it's surely more flexible for users.
Actually talking about performance you may say this is in contrast with what I said before, and it's true, but when talking about library APIs we need to consider that most users will have simpler implementations compared to generic wrappers, so we could say that we may have a smaller binary size and less overhead.
Conclusions
While using Box dyn could look simpler, especially for Rust newbies, and in some cases is better for binary sizes, generics should be preferred instead. Actually it's interesting how the compiler usually gives you this hint
error[E0782]: expected a type, found a trait
--> src/main.rs:26:3
|
26 | greet: Greet
| ^^^^^
|
help: you can add the `dyn` keyword if you want a trait object
|
26 | greet: dyn Greet
| +++
as we've seen before, but doesn't mention that Generics could be even more suitable for this purpose, giving newbies a non-optimal hint to deal with this case. Indeed, even I used Box dyn a lot in place of generics in my early days with Rust.



