When passing a parameter for a type that implements YourTrait
, you can make the parameter's type &dyn YourTrait
as others have said. In this case the instance of the type (that implements the trait) does not have to be Boxed separately. It may be in a local variable on the stack, in a Vec containing objects of that type, etc.
But a function that returns an impl Trait
must always returns the same type. It can't return different types, depending on the parameter as you're trying to do. (Note that a trait is not a type, its a bound for a type.)
That's why, to return different types from a single function you need to Box the object when returning it. This allows you to return a dyn
pointer, which means the function return value will be Box<dyn YourTrait>
, not impl YourTrait
. This forces you to Box each object separately, meaning that each object is allocated separately. One reason for this restriction is that different types that implement a common trait may have different sizes.
However, there is a great way to work around this and avoid the Boxing/allocation, while at the same time improving performance: use the enum_dispatch crate. This only works when there is a known set of types that implement a given trait, but often that is the case. See the example at the link. You create an enum containing the different types that implement a specific trait, and the #[enum_dispatch]
macro does the rest of the work. The performance gain comes from two things: avoiding allocation, and not having to call through a dispatch table like dyn
uses. This crate is extremely popular and there are no drawbacks to using it, other than the restriction I mentioned about having a fixed set of types, that are known at compile time, that implement the trait.
Here's a quick example where the Store trait types are converted to use enum_dispatch. I didn't convert the KeyTrait types. I removed all the boxing.
The important thing to notice is that the enum_dispatch macro has implemented the Store trait for the StoreEnum type, so you can pass a StoreEnum for a parameter with a Store trait bound. The generated implementation efficiently forwards the trait method call to the variant of the enum that is used. This forwarding is faster than calling via a &dyn
reference.
use enum_dispatch::enum_dispatch;
// Define the itemTrait
pub trait KeyTrait {}
#[enum_dispatch(StoreEnum)]
pub trait Store {
// Modify the put method signature to include an item of itemTrait
fn put(&self, item: &dyn KeyTrait);
}
#[enum_dispatch]
enum StoreEnum {
AStore,
BStore,
}
pub struct AStore {}
impl Store for AStore {
fn put(&self, item: &dyn KeyTrait) {
println!("A put");
}
}
pub struct BStore {}
impl Store for BStore {
fn put(&self, _item: &dyn KeyTrait) {
println!("B put");
}
}
pub struct FooKey {}
impl KeyTrait for FooKey {}
pub fn run_with_store<T>(s: T)
where
T: Store,
{
s.put(&FooKey {});
}
pub fn get_store(store_type: &str) -> StoreEnum {
match store_type {
"A" => AStore {}.into(),
_ => BStore {}.into(),
}
}
fn main() {
run_with_store(get_store("A"));
}