Implementing Strategy pattern in rust without knowing which strategy are we using at compile time

292 Views Asked by At

I've been trying to implement a Strategy pattern in rust, but I'm having trouble understanding how to make it work.

So let's imagine we have a trait Adder and Element:

pub trait Element {
    fn to_string(&self) -> String;
}

pub trait Adder {
    type E: Element;

    fn add (&self, a: &Self::E, b: &Self::E) -> Self::E;
}

And we have two implementations StringAdder with StringElements and UsizeAdder with UsizeElements:

// usize
pub struct UsizeElement {
    pub value: usize
}

impl Element for UsizeElement {
    fn to_string(&self) -> String {
        self.value.to_string()
    }
}

pub struct UsizeAdder {
}

impl Adder for UsizeAdder{
    type E = UsizeElement;

    fn add(&self, a: &UsizeElement, b: &UsizeElement) -> UsizeElement{
        UsizeElement { value: a.value + b.value }
    }
}

// String
pub struct StringElement {
    pub value: String
}

impl Element for StringElement {
    fn to_string(&self) -> String {
        self.value.to_string()
    }
}


pub struct StringAdder {
}

impl Adder for StringAdder {
    type E = StringElement;

    fn add(&self, a: &StringElement, b: &StringElement) -> StringElement {
        let a: usize = a.value.parse().unwrap();
        let b: usize = b.value.parse().unwrap();

        StringElement {
            value: (a + b).to_string()
        }
    }
}

And I want to write a code that uses trait methods from Adder trait and it's corresponding elements without knowing at compile time which strategy is going to be used.

fn main() {
    let policy = "usize";
    let element = "1";

    let adder = get_adder(&policy);
    let element_a = get_element(&policy, element);

    let result = adder.add(element_a, element_a);
}

To simplify I'm going to assign a string to policy and element but normally that would be read from a file.

Is the only way to implement get_adder and get_element using dynamic dispatch? And by extension should I define Adder and Element traits to use trait objects and or the Any trait?

Edit: Here is what I managed to figure out so far.

An example of possible implementation is using match to help define concrete types for the compiler.

fn main() {
    let policy = "string";
    let element = "1";
    let secret_key = "5";

    let result = cesar(policy, element, secret_key);

    dbg!(result.to_string());
}

fn cesar(policy: &str, element: &str, secret_key: &str) -> Box<dyn Element>{
    match policy {
        "usize" => {
            let adder = UsizeAdder{};
            let element = UsizeElement{ value: element.parse().unwrap() };
            let secret_key = UsizeElement{ value: secret_key.parse().unwrap() };
            Box::new(cesar_impl(&adder, &element, &secret_key))
        }
        "string" => {
            let adder = StringAdder{};
            let element = StringElement{ value: element.to_string() };
            let secret_key = StringElement{ value: secret_key.to_string() };
            Box::new(cesar_impl(&adder, &element, &secret_key))
        }
        _ => {
            panic!("Policy not supported!")
        }
    }
}

fn cesar_impl<A>(adder: &A, element: &A::E, secret_key: &A::E) -> A::E where A: Adder, A::E : Element {
    adder.add(&element, &secret_key)
}

However the issue is that I have to wrap every function I want to implement using a match function to determine the concrete type, and also case for every policy available.

It does not seem like the proper way of implementing it as it will bloat the code, make it more error prone and less maintainable unless I end up using macros.

Edit 2: Here you can find an example using dynamic dispatch. However I'm not convinced it's the proper way to implement the solution.

Example using dynamic dispatch

Thank you for your help :)

0

There are 0 best solutions below