Reputation: 620
I'm trying to use the AES crate, which offers three algorithms: AES128, AES192 and AES256. I'm trying to create a struct that can create the correct algorithm by detecting the key size, and save it to later use.
I see they all implement the BlockEncrypt (I only need encryption) trait, but when I try to make a field in the struct with this type, even when supplying the size, i get an "the trait BlockEncrypt
cannot be made into an object. the trait cannot be made into an object because it requires Self: Sized
" error.
pub struct MyStruct<'a, T: Sized> {
ciph: Box< dyn BlockEncrypt<BlockSize = T>>,
}
Upvotes: 4
Views: 1063
Reputation: 169353
Traits that have Sized
as a supertype aren't "object safe" which means dynamic dispatch using dyn
isn't possible on these kinds of traits.
You can accomplish this relatively tersely by using an enum for each possible type you want to handle, combined with a macro that will neatly generate all the match
arms required to dynamically dispatch on each enum possibility.
To make things simple for the sake of this explanation, I'm going to define a new trait (with the Sized
supertype) so that this answer doesn't depend on a particular crate:
trait SampleTrait: Sized {
fn method_a(&self, foo: &str);
fn method_b(&self, foo: i32, bar: String) -> String;
}
Now we need at least two implementations of this trait to demonstrate the solution:
struct ImplA;
struct ImplB;
impl SampleTrait for ImplA {
fn method_a(&self, foo: &str) {
println!("<ImplA as SampleTrait>::method_a(): {:?}", foo);
}
fn method_b(&self, foo: i32, bar: String) -> String {
println!("<ImplA as SampleTrait>::method_b(): {:?} {:?}", foo, bar);
format!("from ImplA: {}", bar)
}
}
impl SampleTrait for ImplB {
fn method_a(&self, foo: &str) {
println!("<ImplB as SampleTrait>::method_a(): {:?}", foo);
}
fn method_b(&self, foo: i32, bar: String) -> String {
println!("<ImplB as SampleTrait>::method_b(): {:?} {:?}", foo, bar);
format!("from ImplB: {}", bar)
}
}
The enum just needs to least each possible type that you need to handle:
enum DynSampleTrait {
ImplA(ImplA),
ImplB(ImplB),
}
Now to dispatch on this, you'd have to do something like:
match value {
DynSampleTrait::ImplA(v) => v.method_a("something"),
DynSampleTrait::ImplB(v) => v.method_a("something"),
}
To get around having to repeat ourselves, let's declare a macro that will generate the whole match
for us:
macro_rules! dyn_sample_trait_call {
(($m:expr) $v:ident => $code:block) => {
match $m {
DynSampleTrait::ImplA($v) => $code,
DynSampleTrait::ImplB($v) => $code,
}
}
}
Now we can use the macro like so:
dyn_sample_trait_call!((value) v => { v.method_a("something") })
value
goes in the match expression, and v
is the identifier that unwraps the inner value held by the enum possibility. These are separate so that you can match over a reference, should you not want to consume an owned DynSampleTrait
:
dyn_sample_trait_call!((&value) v => { v.method_a("something") })
Upvotes: 5
Reputation: 5447
As @cdhowie mentioned, you can't create a trait object from a trait with a Sized
bound. Instead, you can create an enum:
enum MyAlgorithm {
AES128(AES128),
AES192(AES192),
AES256(AES256),
}
Upvotes: 3