(Sorry for the long post; not totally sure how to simplify further).
I'm trying to implement a couple of different versions of the same abstract "protocol" against each other.
Imagine this interface:
trait Protocol {
type Message;
fn generate(&self) -> Self::Message;
fn verify(&self, m: Self::Message) -> bool;
}
We want to use it as (in practice, this complicated enough to be an entire program, so I really need to reuse this bit):
fn run_protocol<P: Protocol>(protocol: P) -> bool {
let message: P::Message = protocol.generate();
protocol.verify(message)
}
We can give a few trivial implementations (note that in some cases there are protocol parameters, so we do actually need the protocol methods to be methods):
struct Foo;
impl Protocol for Foo {
type Message = bool;
fn generate(&self) -> bool {
true
}
fn verify(&self, m: bool) -> bool {
m
}
}
struct Bar {
password: u32,
}
impl Protocol for Bar {
type Message = u32;
fn generate(&self) -> u32 {
self.password
}
fn verify(&self, m: u32) -> bool {
m == self.password
}
}
And then running is easy:
fn main() {
let foo = Foo {};
assert!(run_protocol(foo));
let bar = Bar { magic: 42 };
assert!(run_protocol(bar));
}
So far, so good.
Now, I want to have my main()
pick a protocol based on a config file, so I try to make a little factory method:
fn make_protocol(is_foo: bool) -> Box<dyn Protocol> {
if is_foo {
Box::new(Foo {})
} else {
Box::new(Bar { magic: 42})
}
}
This makes rustc
unhappy, as "the value of the associated type Message
(from the trait Protocol
) must be specified." Makes sense, especially since it'd be impossible to stack-allocate space for Message
if you called the boxed protocol's generate()
method without knowing what it is.
But what's the right way to this? I can imagine a few ways:
-
(dynamic dispatch) Make the protocol method return heap-allocated values using
Box::new
.
This is a little distracting while implementingProtocol
, and doesn't actually solve the underlying problem: I still don't know how to specify the return type ofmake_protocol
. -
(simulated dynamic dispatch) Make the protocol return an
Enum
of all the allowed message types, so it doesn't actually need an associated type. This seems to remove the benefit of having an associated type at all. -
(single-dispatch) I could do away with
make_protocol
altogether, and just have a bigmatch
over the contents of the config file that looks a bit like themain()
above. This seems really hacky to me but would work and maybe be faster.
In practice, I think I'd want to maybe stick in a little shim struct that uses the above trick we can leave our protocol unmodified. I'm open to other ideas, of course; I'm not enthralled with any of the above.