Counter

This tutorial will introduce the two most important concepts of the actions library:

A State describes (a part) of the state of an application.
Actions describe how the state of an application should change.

Some setup

First make sure you are using the actions crate. Make sure to have actions as a dependency in your Cargo.toml file. If you are unfamiliar with Cargo, check out the Cargo book).

[dependencies]
actions = "0.2.0"

Then make sure to specify actions as a dependency in your project, and import the State trait.


# #![allow(unused_variables)]
#fn main() {
use actions::State;
#}

Building the counter

One of the easiest examples for explaining actions is a counter. The counter has two buttons: an decrement button, and an increment button.

An example of what a counter in an application might look like

Our task is to write the logic for the counter. Easy!

Step one: Defining the State

One of the most important aspect of the counter is storing the value displayed in the counter. We will use an integer to store the value.

As the counter holds just one value, it can be described like this:


# #![allow(unused_variables)]
#fn main() {
struct Counter(u32);
#}

Step two: Defining the actions

Maybe even more important than the counter are the two buttons.

The decrement button should decrement the counter by 1, and the increment button should increment the counter by 1.

Incrementing and decrementing are actions. It is immediately clear how executing those actions influences the State and therefore the state of the application.

Let's convert these actions into Rust-code.


# #![allow(unused_variables)]
#fn main() {
enum CounterAction {
    Increment,
    Decrement,
}
#}

That's it! You've defined your actions.

Step three: Defining the behaviour of the State

Step three is defining how the state of the State should change when an action is executed.

Handling errors

To make sure the counter does not go past the maximum or minimum value, we will return an eror if it would. Errors are easily represented using an enum:


# #![allow(unused_variables)]
#fn main() {
enum CounterError {
    MinValueReached,
    MaxValueReached,
}
#}

We will use this error-type later.

Implementing the State trait

Counter is a State, as it describes the state of our application. Therefore, we will implement the State trait for it.


# #![allow(unused_variables)]
#fn main() {
impl State for Counter {
    // Define what actions influence this State
    type Action = CounterAction;
    type Error = CounterError;

    // The function that applies the action!
    fn apply(&mut self, action: &CounterAction) -> Result<(), CounterError> {
        match action {
            CounterAction::Increment => match self.0.checked_add(1) {
                Some(new_value) => self.0 = new_value,
                None => Err(CounterError::MaxValueReached)?,
            },
            CounterAction::Decrement => match self.0.checked_sub(1) {
                Some(new_value) => self.0 = new_value,
                None => Err(CounterError::MinValueReached)?,
            },
        };
        Ok(())
    }
}
#}

Note that the apply-function is returning the inverse of the action that was performed. This can be used to 'undo' the action (will be discussed in another tutorial).

If the action has no effect on the state of the State, the apply function should return None.

Testing the counter

That was all you need for the logic of the counter. Now, let's test it.


# #![allow(unused_variables)]
#fn main() {
fn test() -> Result<(), CounterError> {
    // Create a new counter with an initial value of 0.
    let mut counter = Counter(0);

    counter.apply(&CounterAction::Increment)?;
    counter.apply(&CounterAction::Increment)?;
    counter.apply(&CounterAction::Decrement)?;
    assert_eq!(1, counter.0);

    counter.apply(&CounterAction::Decrement)?;

    // This should cause our own error message to be printed,
    // because the counter uses an unsigned integer (cannot be negative).
    counter.apply(&CounterAction::Decrement)?;

    Ok(())
}
#}

It works! You can find the final code from this tutorial here.