Counter App with Actions
One of the first steps to building truly async
TUI applications is to use the Command
, Action
,
or Message
pattern.
You can learn more about this concept in The Elm Architecture section of the documentation.
We have learnt about enums in JSON-editor tutorial. We are going to extend the counter application
to include Action
s using Rust’s enum features. The key idea is that we have an Action
enum that
tracks all the actions that can be carried out by the App
. Here’s the variants of the Action
enum we will be using:
Now we add a new get_action
function to map a Event
to an Action
.
And the update
function takes an Action
instead:
Here’s the full single file version of the counter app using the Action
enum for your reference:
While this may seem like a lot more boilerplate to achieve the same thing, Action
enums have a few
advantages.
Firstly, they can be mapped from keypresses programmatically. For example, you can define a
configuration file that reads which keys are mapped to which Action
like so:
Then you can add a new key configuration like so:
If you populate keyconfig
with the contents of a user provided toml
file, then you can figure
out which action to take by updating the get_action()
function:
Another advantage of this is that the business logic of the App
struct can be tested without
having to create an instance of a Tui
or EventHandler
, e.g.:
In the test above, we did not create an instance of the Terminal
or the EventHandler
, and did
not call the run
function, but we are still able to test the business logic of our application.
Updating the app state on Action
s gets us one step closer to making our application a “state
machine”, which improves understanding and testability.
If we wanted to be purist about it, we would make a struct called AppState
which would be
immutable, and we would have an update
function return a new instance of the AppState
:
Like in Charm
, we may also want to choose a action to follow up after an update
by returning
another Action
:
We would have to modify our run
function to handle the above paradigm though. Also, writing code
to follow this architecture in Rust requires more upfront design, mostly because you have to make
your AppState
struct Clone
-friendly.
For this tutorial, we will stick to having a mutable App
:
The other advantage of using an Action
enum is that you can tell your application what it should
do next by sending a message over a channel. We will discuss this approach in the next section.