The source content for blog.juliobiason.me
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3.4 KiB

+++ title = "Command Pattern Experiments in Rust" date = 2021-07-22

[taxonomies] tags = ["design patterns", "command", "rust"] +++

I've been playing a bit with the command pattern in Rust and found at least two ways to write it.

But first... why?

There is one thing I'm trying to do with all this: I want to have a library with all the actions and then plug interfaces on top of, being either a CLI interface or a Web interface or whatever interface. For that, the logic behind the action should be somewhat isolated from whatever source it is calling it. And the command interface fits perfectly in this.

The Enum Solution

Because you have a list of actions, one of the ideas was to use Enums.

Say, we have two actions the user can trigger: deposit money and withdraw money. Simple.

So one could have the following Enum:

enum Command {
    Deposit(Decimal),
    Withdraw(Decimal),
}

Because Rust allows enum variants to carry a value with them, the amount to be deposited/withdraw is attached directly to the variant.

And then you have the execute() function. And, again, 'cause Rust allows adding functions to almost everything, what I did was:

impl Command {
    fn execute(&self) -> Result<...> {
        match self {
            Deposit(value) => do_the_deposit(value),
            Withdraw(value) => withdraw_money(value),
        }
    }
}

... and so on.

It feels fine and all, but it tends to make a mess with the amount of content that goes in or around the impl, in my opinion. But, at the same time, means that our interface layer must just return Command and that's all fine.

One solution to the amount of "content in or around impl" could be use multiple impl: So I could have a module deposit.rs which impls the do_the_deposit and another module withdraw.rs which also impls inside the enum with the withdraw_money content. But I'd still need the center execute to do the proper "dispatch" of the calls.

The Trait Solution

The trait solution is very close to what the pattern is: You create a trait and "impl" it for all the commands. For example:

trait Command {
    fn execute(&self) -> Result<...>;
}
struct Deposit(Decimal);
impl Command for Deposit {
    fn execute(&self) -> Result<...> {
        // what was `do_the_deposit` now goes here.
    }
}

struct Withdraw(Decimal);
impl Command for Withdraw {
    fn execute(&self) -> Result<...> {
        // what was `withdraw_money` now goes here.
    }
}

This causes a slight problem with the interface layer: Now it can't just return one sized thing: It needs to return a dynamic dispatchable content, like Box<dyn Command>, which isn't as pretty as the direct Enum/Struct/sized content from the Enum.

On the other hand, since Box implements Deref, once the interface throws something-that-implements-Command, one could just call execute() directly on it.

let command = interface_that_returns_a_box_dyn_command();
command.execute();

Also, because all commands are structures, they can hold the same information as Enum variants would.

Where I see those two

I can see the Enum being used for simple, single domain architectures. Since all things are related, they can reside correctly under the Enum.

But when dealing with multiple domains, the trait/dynamic dispatch feels more at home: Related things get into their own module, in their own space and the idea of mixing them goes on layer above.