Counter
This tutorial will introduce the two most important concepts of the actions library:
A component 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.0.2"
Then make sure to specify actions as a dependency in your project, and import the Component trait. We will also need action's Error.
# #![allow(unused_variables)] #fn main() { extern crate actions; use actions::Component; use actions::Error; #}
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.
Our task is to write the logic for the counter. Easy!
Step one: Defining the component
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(i32); #}
Step two: 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 component and therefore the state of the application.
Let's convert these actions into Rust-code.
# #![allow(unused_variables)] #fn main() { #[derive(Clone)] enum CounterAction { Increment, Decrement, } #}
That's it! You've defined your actions.
Step three: Defining the behaviour of the component
Step three is defining how the state of the component should change when an action is executed.
Implementing the Component trait
Counter is a Component, as it describes the state of our application. Therefore, we will implement the Component trait for it.
# #![allow(unused_variables)] #fn main() { impl Component for Counter { // Define what actions influence this component type Action = CounterAction; // The function that applies the action! // This is where the magic happens. fn apply(&mut self, action: &CounterAction) -> Result<Option<Self::Action>, Error> { match action { CounterAction::Increment => { self.0 += 1; Ok(Some(CounterAction::Decrement)) } CounterAction::Decrement => { self.0 -= 1; Ok(Some(CounterAction::Increment)) } } } } #}
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 component, 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<(), Error> { // Create a new counter with an initial value of 0. let mut counter = Counter(0); counter.apply(&CounterAction::Increment)?; assert_eq!(counter.0, 1); counter.apply(&CounterAction::Increment)?; assert_eq!(counter.0, 2); counter.apply(&CounterAction::Decrement)?; assert_eq!(counter.0, 1); Ok(()) } #}
It works! You can find the final code from this tutorial here.