I'm writing some program accepting 2+ formats of input data and then doing some generic processing on it. Naturally, I've designed something resembling:
trait Processor {
fn process(input: &str) -> Option<String>;
}
struct Horse {}
impl Processor for Horse { /* ... */ }
struct Dog {}
impl Processor for Dog { /* ... */ }
fn do_magic(input: &str) -> Option<String> {
// Pseudo-code, this obviously does not compile.
for dyn one_impl in [Horse, Dog] /* order is important */ {
if let Some(out) = one_impl::process(input) {
return Some(out);
}
}
None
}
I'm trying to understand how I could write do_magic to do that kind of "iterate over implementations of Processor in that order and return the first output that succeeds".
I currently can only think of methods I find suboptimal/ugly:
have a bunch of ifs for each implementation similar to
if let Some(out) = Horse::process(input) { return Some(out); }
if let Some(out) = Dog::process(input) { return Some(out); }
None
do just that, but with some macro_rules!
This example is minimal and has only 2 impls, so it does not look that bad, but IRL I have more impls and extra generic stuff going on for each, making for pretty terrible code using that method.
I feel like I'm missing something obvious, any idea? Thanks!
Hm, admitted, I’ve probably over-engineered this. You could simply do
fn do_magic(input: &str) -> Option<String> {
for process in [Horse::process, Dog::process] {
if let Some(out) = process(input) {
return Some(out);
}
}
None
}
I think we need talk about what kind of stuff this “extra generic stuff” is; in particular if your Processor trait has more than one method; possibly even associated types?
Edit:
As long as it’s just multiple methods, you could do something like
Is there a reason you couldn’t use regular dyn polymorphism here? You’d just need to give the process methods a self parameter and then have a plain old list of objects rather than a typelist or other exotics?
@steffahn, like you mentioned in your reply, indeed I don't have just a single method, otherwise I'd just have built a static slice of references to a generic method indeed. Sorry my original post was incomplete.
@jdahlstrom, this seems to be similar to what @steffahn ended up suggesting in post #4.
But maybe you're referring to something simpler like this (or using Box<dyn>)? Rust Playground – I could indeed get behind that, thanks for the suggestion, is there anything I could improve there?
I do find it sad that I need to carry around these physical instances, when I "just" wanted a way of having a compile-time (static) for-loop that refers to "types".
Yes, indeed I was, just the way you’d do it in a standard issue OOP language like Java. steffan’s array-of-function-pointers is also fine as long as you don’t need several related functions in one entity – which is of course what objects are. Note also that as long as the structs are stateless (ie. unit structs), you don’t really need to care about the instances because unit structs are necessarily singletons, and in Rust the sole value of a unit struct has the same name as the type. Hence:
The major difference being that only func2 could be a trait method callable through dyn Trait because the self argument is needed to get to the correct vtable.