javarustdesign-patternsstrategy-pattern

Rust equivalent of Java Consumer interface for Strategy pattern


I will provide a practical example.

I want to create an event logger. I define an event as an Interface:

import java.util.function.Consumer;

interface Event {}

class BuyEvent implements Event {}

class SellEvent implements Event {}

A logger is simply a Consumer of events:

public static void main(String[] args) {
        Consumer<Event> logger;

        // Logger with method reference
        logger = System.out::println;
        logger.accept(new BuyEvent());

        // Logger with lambda
        logger = (event) -> {
            // Do something else
            System.out.println(event);
        };
        logger.accept(new BuyEvent());

I can also create a logger with state. For example:

class StatefulLogger implements Consumer<Event> {
    public StatefulLogger() {
    }

    @Override
    public void accept(Event event) {
        // Change state, then print event
        System.out.println(event);
    }
}

I can use the stateful logger as follows:

public static void main(String[] args) {
        Consumer<Event> logger = new StatefulLogger("foo.txt");
        logger.accept(new BuyEvent());
}

I am trying to achieve the same in Rust.

I define an event as an enum:

#[derive(Debug)]
enum Event {
    BuyEvent,
    SellEvent,
}

I define a Logger trait and a struct with state that implements such trait:

trait Logger {
    fn accept(&mut self, ev: Event);
}

struct StatefulLogger {}

impl Logger for StatefulLogger {
    fn accept(&mut self, ev: Event) {
        // Change state, then print event
        println!("{:?}", ev);
    }
}

I can use the logger as follows:

fn main() {
    let logger: &dyn Logger = &ComplexLogger {};
}

I would like to be able to assign a closure to the logger, much in the same spirit of Java.

fn main() {
    let consumer: fn(Event) = |ev: Event| {
        println!("{:?}", ev);
    };
}

To recap:

In Java, I had implemented a logger using a Strategy design pattern using the Consumer interface. A logger could both be a complex stateful object but also a lightweight lambda.

I would like to achieve the same in Rust, but I don't know how to proceed. I was not able to find similar examples on the Internet. Also, does Rust provide an analogous to Java's Consumer interface?


Solution

  • There is no way to solve this fast in Rust, but you can create own macro to convert lambda to Logger impl and then return its object. Have a look at this repo. If you want, I can write a solution soon. In other way you can change your Logger trait to struct, where define field for consumer. It gives you possibility to use as consumer any Fn(Event) i.e if you have logger with state, you can move it to lambda and pass it or pass some fn directly.

    #[derive(Debug)]
    struct Event;
    
    struct Logger<T>
    where
        T: Fn(Event),
    {
        consumer: T,
    }
    
    impl<T> Logger<T>
    where
        T: Fn(Event),
    {
        fn new(consumer: T) -> Self {
            Self { consumer }
        }
    
        fn accept(&self, event: Event) {
            (self.consumer)(event);
        }
    }
    
    struct StatefulLogger;
    
    impl StatefulLogger {
        fn log(&self, event: Event) {
            println!("{:?}", event);
        }
    }
    
    fn log_fn(e: Event) {
        println!("{:?}", e);
    }
    
    fn main() {
        let foo = |e: Event| println!("{:?}", e);
        let logger_lambda = Logger::new(foo);
    
        let stateful_logger = StatefulLogger;
        let logger_struct = Logger::new(move |e| stateful_logger.log(e));
    
        let logger_fn = Logger::new(log_fn);
    
        logger_lambda.accept(Event);
        logger_struct.accept(Event);
        logger_fn.accept(Event);
    }
    

    About consumer interface, have a look on Fn, FnMut and FnOnce traits.