Reputation: 325
I have a trait implemented for many structs (A
, B
, C
, etc.):
pub trait ApplicableFor: Debug + Default {
unsafe fn is_applicable_for(from: *mut u8) -> bool
where
Self: Sized;
}
I need a method which finds which struct returns true for this method call. I have the code:
unsafe fn check_applicable<T: ApplicableFor>(from: *mut u8) -> bool {
T::is_applicable_for(from, to)
}
unsafe fn find_applicable(from: *mut u8) -> ApplicableFor {
if check_applicable::<A>(from) {
A::default()
} else if check_applicable::<B>(from) {
B::default()
} else if check_applicable::<C>(from) {
C::default()
} else {
panic!("Couldn't find appicable");
}
}
In the real code, I have around 20 structs, so I want to store them somewhere and use code like this for readability:
unsafe fn find_applicable(from: *mut u8) -> ApplicableFor {
for T in list {
if check_applicable::<T>(from) {
T::default()
}
}
panic!("Couldn't find appicable");
}
How can I do this or how can I rewrite this better?
Upvotes: 0
Views: 67
Reputation: 430671
No, Rust does not directly offer the kind of metaprogramming features you need. Namely, a type is not a concrete thing that exists or can be put into a collection.
Instead, you need code generation.
Starting from a simplified version of ApplicableFor
, we can write a very structured version of find_applicable
:
trait ApplicableFor {
fn is_applicable_for(from: u8) -> bool;
}
fn find_applicable(from: u8) {
if <A>::is_applicable_for(from) {
println!("Using {}", stringify!(A));
return;
}
if <B>::is_applicable_for(from) {
println!("Using {}", stringify!(B));
return;
}
if <C>::is_applicable_for(from) {
println!("Using {}", stringify!(C));
return;
}
panic!("Couldn't find any applicable types");
}
Once we have established the structure, then we can start abstracting it with macros:
fn find_applicable(from: u8) {
macro_rules! find_one {
($ty:ty) => {
if <$ty>::is_applicable_for(from) {
println!("Using {}", stringify!($ty));
return;
}
}
}
find_one!(A);
find_one!(B);
find_one!(C);
panic!("Couldn't find any applicable types");
}
What if we want to repeat this concept of "do something for this list of types"? Another macro:
macro_rules! each_type {
($one_type_macro:tt) => {
$one_type_macro!(A);
$one_type_macro!(B);
$one_type_macro!(C);
};
}
fn find_applicable(from: u8) {
macro_rules! find_one {
($ty:ty) => {
if <$ty>::is_applicable_for(from) {
println!("Using {}", stringify!($ty));
return;
}
}
}
each_type!(find_one);
panic!("Couldn't find any applicable types");
}
Too much noise for the implementation of each_type!
? Create a macro that creates another macro which will be called with another macro:
macro_rules! gen_each_type {
($($ty:ty),*) => {
macro_rules! each_type {
($one_type_macro:tt) => {
$($one_type_macro!($ty);)*
};
}
};
}
gen_each_type![A, B, C];
Upvotes: 3