Command Pattern in Rust and Lifetime issues

When I try to implement a simple Rust sample of Command Pattern following the book Head First Design Patterns, the issues of lifetime really drive me crazy. I think I still cannot fully understand the lifetime in Rust. Can someone be so kind as to point out where I did wrong or give me a better implementation? Thanks.
The Java codes in Head First Design Patterns are simple as below:

public interface Command {
    void execute();
}
public class RemoteControl {
    private Command[] onCommands= new Command[7];
    private Command[] offCommands = new Command[7];
    public void setCommand(int slot, Command onCommand,Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }
    public void onButtonPressed(int slot) {
        onCommands[slot].execute();
    }
    public void offButtonPressed(int slot) {
        offCommands[slot].execute();
    }
}

public class Light {
    private boolean on;
    private int order;
    public Light(boolean on, int order) {
        this.on = on;
        this.order = order;
    }
    public void switchLight() {
        if (on)
            System.out.printf("Light %d turned off\n", order);
        else
            System.out.printf("Light %d turned on\n", order);
        on = !on;
    }
}

public class LightSwitchCommand implements Command {
    private Light light;
    public LightSwitchCommand(Light light) {
        this.light = light;
    }
    public void execute() {
        light.switchLight();
    }
}

public static void main(String[] args) {
        Light l1 = new Light(true,1);
        Light l2 = new Light(true,2);
        LightSwitchCommand l1switch = new LightSwitchCommand(l1);
        LightSwitchCommand l2switch = new LightSwitchCommand(l2);
        RemoteControl remote = new RemoteControl();
        remote.setCommand(0,l1switch,null);
        remote.setCommand(1,l2switch,null);
        remote.onButtonPressed(0);
        remote.onButtonPressed(1);
}

However, when trying to transfer them into Rust, I am frastrated by issues of lifetime.
My codes are below:

pub struct RemoteControl<'a>{
    on_commands: [Option<Box<dyn MutCommand + 'a>>; 7],
    off_commands: [Option<Box<dyn MutCommand + 'a>>; 7]
}

impl<'a> RemoteControl<'a>{
    pub fn new() -> RemoteControl{  // here comes the lifetime issue
        RemoteControl {
            on_commands:[None;7],
            off_commands:[None,7]
        }
    }
    pub fn set_command(&mut self, slot: i32, on_command: Option<Box<dyn MutCommand + 'a>>, off_command: Option<Box<dyn MutCommand + 'a>>){
        self.on_commands[slot] = on_command;
        self.off_commands[slot] = off_command;
    }

    pub fn on_button_pressed(&mut self, slot: i32){
        match self.on_commands[slot] {
            None => {},
            Some(cmd) => cmd.mut_execute()
        };
    }
    pub fn off_button_pressed(&mut self, slot: i32){
        match self.on_commands[slot] {
            None => {},
            Some(cmd) => cmd.mut_execute()
        };
    }
}

pub trait MutCommand{
    fn mut_execute(&mut self);
}

pub struct SwitchLightCommand<'a>{
    light: &'a mut Light
}

impl<'a> SwitchLightCommand<'a>{
    pub fn new(light: &'a mut Light) -> SwitchLightCommand{
        SwitchLightCommand { light }
    }
}

impl<'a> MutCommand for SwitchLightCommand<'a>{
    fn mut_execute(&mut self){
        self.light.switch();
    }
}

pub struct Light{
    on: bool,
    order: u8
}

impl Light{
    pub fn new(light_on: bool, order: u8) -> Light{
        Light { on: light_on, order }
    }
    pub fn switch(&mut self){
        if self.on{
            println!("Light {} turned off", self.order);
        } else {
            println!("Light {} turned on", self.order);
        }
        self.on = !self.on;
    }
}

The error massge given by the compiler is below. I really don't know how to deal with it. The given help seems not to work.

error[E0106]: missing lifetime specifier
  --> src\command_pattern.rs:16:21
   |
16 |     pub fn new() -> RemoteControl
   |                     ^^^^^^^^^^^^^ help: consider giving it a 'static lifetime: `RemoteControl + 'static`
   |
   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

And it seems that the section talking about lifetime in The book is not very helpful for this situation.

If you write RemoteControl, that elides to RemoteControl<'static>. To fix this either use RemoteControl<'a> or Self. Changing it to self and fixing the comma in off_commands, we get:

pub fn new() -> Self {
    RemoteControl {
        on_commands: [None; 7],
        off_commands: [None; 7]
    }
}

Now you get this error:

error[E0277]: the type `[std::option::Option<std::boxed::Box<(dyn MutCommand + 'a)>>]` cannot be indexed by `i32`
  --> src/main.rs:14:9
   |
14 |         self.on_commands[slot] = on_command;
   |         ^^^^^^^^^^^^^^^^^^^^^^ slice indices are of type `usize` or ranges of `usize`
   |
   = help: the trait `std::slice::SliceIndex<[std::option::Option<std::boxed::Box<(dyn MutCommand + 'a)>>]>` is not implemented for `i32`
   = note: required because of the requirements on the impl of `std::ops::Index<i32>` for `[std::option::Option<std::boxed::Box<(dyn MutCommand + 'a)>>]`

This is because you should use usize, not i32 to index arrays. After fixing this, you'll get two more errors:

error[E0277]: the trait bound `std::option::Option<std::boxed::Box<dyn MutCommand>>: std::marker::Copy` is not satisfied
  --> src/main.rs:10:27
   |
10 |             off_commands: [None; 7],
   |                           ^^^^^^^^^ the trait `std::marker::Copy` is not implemented for `std::option::Option<std::boxed::Box<dyn MutCommand>>`
   |
   = help: the following implementations were found:
             <std::option::Option<T> as std::marker::Copy>
   = note: the `Copy` trait is required because the repeated element will be copied

and

error[E0507]: cannot move out of `self.on_commands[_].0` which is behind a mutable reference
  --> src/main.rs:19:15
   |
19 |         match self.on_commands[slot] {
   |               ^^^^^^^^^^^^^^^^^^^^^^ help: consider borrowing here: `&self.on_commands[slot]`
20 |             None => {},
21 |             Some(cmd) => cmd.mut_execute()
   |                  ---
   |                  |
   |                  data moved here
   |                  move occurs because `cmd` has type `std::boxed::Box<dyn MutCommand>`, which does not implement the `Copy` trait

The first error is because when using arrays, you can only use the [value; count] syntax if the value is copyable. In principle it would be ok because they're all None, but it's the type that matters, and Option<Box<dyn MutCommand + 'a>> is not a copyable type.

This is the reason why vectors are so much better than arrays, but in this case, the simplest solution is to just repeat it seven times.

As for the other error, it's because you should be using match &mut self.on_commands[slot]. The reference is important, as you're otherwise taking the value out of the array, which you can't in this context, and it should be a mutable reference as you're modifying the value stored in on_commands.

Finally we get this, which compiles!

impl<'a> RemoteControl<'a> {
    pub fn new() -> Self {
        RemoteControl {
            on_commands: [None, None, None, None, None, None, None],
            off_commands: [None, None, None, None, None, None, None],
        }
    }
    pub fn set_command(&mut self, slot: usize, on_command: Option<Box<dyn MutCommand + 'a>>, off_command: Option<Box<dyn MutCommand + 'a>>){
        self.on_commands[slot] = on_command;
        self.off_commands[slot] = off_command;
    }

    pub fn on_button_pressed(&mut self, slot: usize){
        match &mut self.on_commands[slot] {
            None => {},
            Some(cmd) => cmd.mut_execute()
        };
    }
    pub fn off_button_pressed(&mut self, slot: usize){
        match &mut self.on_commands[slot] {
            None => {},
            Some(cmd) => cmd.mut_execute()
        };
    }
}

playground

3 Likes

You should be aware that as Rust has no garbage collector, it requires the ownership of everything to be well defined. This makes this pattern ineffective in Rust. Given this main function:

fn main() {
    let mut l1 = Light::new(true, 1);
    let mut l2 = Light::new(true, 2);
    let l1switch = SwitchLightCommand::new(&mut l1);
    let l2switch = SwitchLightCommand::new(&mut l2);
    let mut remote = RemoteControl::new();
    remote.set_command(0, Some(Box::new(l1switch)), None);
    remote.set_command(1, Some(Box::new(l2switch)), None);
    remote.on_button_pressed(0);
    remote.on_button_pressed(1);
}

Some of the issues you may run in to when expanding on this include:

  1. As l1 and l2 are mutably borrowed inside remote, they are completely locked and cannot be accessed or modified in any other way than through remote.
  2. Since remote borrows from l1 and l2, this variable cannot outlive either l1 or l2. This means you must destroy remote before the scope of l1 and l2 ends.
  3. Since the SwitchLightCommand borrows the corresponding Light mutably, no other places can borrow that light. This means you are not able to create a command for switching it off unless you find a way to make the same object perform both on and off commands.

Some of these issues can be tackled by using these types:

  1. std::rc::Rc — Reference counting used to share ownership between several places.
  2. std::cell::RefCell — Data inside an Rc can only be modified if done through something like a RefCell. It enforces that only one location in the code is modifying the contents at a time.
  3. std::cell::Cell ­— A simpler-to-use version of RefCell that only works on types that you can copy.
  4. std::sync::Arc and std::sync::Mutex ­— Multi-threaded versions of Rc and RefCell.
  5. std::sync::atomic::* ­— Special versions of the integers that allow modification from several places and threads at the same time.

Thank you very much! It's very comprehensive! As you mentioned, we can use pub fn new() -> Self or pub fn new() -> RemoteControl<'a>. Are these two equivalent? And why dose pub fn new(light_on: bool, order: u8) -> Light work? Is it better that we use Self as the return type?

Yes, I would recommend always using Self. This is always the same as using whatever was written in the definition of the impl block:

impl<'a> RemoteControl<'a> {
         ^^^^^^^^^^^^^^^^^

So yes, it's the same as RemoteControl<'a>. As for Light, it's because there's no lifetime in play, so the impl block looks like this:

impl Light {
     ^^^^^

Note that you can also use Self inside the function:

pub fn new() -> Self {
    Self { // <-- here
        on_commands: [None; 7],
        off_commands: [None; 7]
    }
}

although you cannot put lifetimes in that position, so it's less critical here.

I thought about those, and they are definitely better solutions in complex implementations if we don't take performance overhead into consideration. I considered these codes as a simple sample, so I did not bother with those. But thanks for the reminder!

As you mentioned write RemoteControl, that elides to RemoteControl<'static>, if we write codes like new()->Light, will it elide to Light<'static>? even though there's no lifetime in play. If so, will this cause unnecessary memory occupation when the program runs?

No, there is no such thing as Light<'static>. This is only relevant for types with lifetimes.

Got that! And thanks again.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.