| Crates.io | rustey |
| lib.rs | rustey |
| version | 0.1.1 |
| created_at | 2025-04-29 18:14:08.369475+00 |
| updated_at | 2025-06-19 21:10:59.641721+00 |
| description | An implementation of the Elm architecture in Rust |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1653884 |
| size | 27,781 |
TUIs with Ratatui and the Elm architecture.
The Elm Architecture consists of a small number of components that play nicely
together. The core element is the model. When the application starts the init
function is called to retrieve the initial model. After initialization the
view function is called to render the user interface. Once rendering is done
the application will wait for events. Events are emitted by the functions the
application is subscribed to. The subscriptions function will emit a number
of such functions based on the state of the model. Each function will run in a
thread of it's own and emit events as relevant.
The model is updated by the update function as it reacts to messages. After
each update the subscriptions function is called again allowing the update of
which subscriptions are runningl.
Apart from the subscription the model may also return a command, which is a task that will be executed asynchronously using threads. When the task is complete it may emit an event for the update function to handle.
To use Rustey in your project, add rustey, ratatui and crossterm to your
Cargo.toml file:
cargo add rustey ratatui crossterm
The first thing you need to do is to create a model. The model is a struct that contains the state of your application. You can create a model like this:
struct Model {
counter: i32,
}
You will then need to define which messages the application can handle. Messages are the events that the application can react to. You can define messages like this:
enum Msg {
Increment,
Decrement,
Quit,
}
With the model and the messages in place you can define an application struct.
struct MyApp {}
And then implement the RusteyApp trait for your application. The RusteyApp trait for the application struct:
impl RusteyApp<Model, Msg> for MyApp {
fn init(&self) -> (Model, Cmd<Msg>) {
(Model { counter: 0 }, Cmd::None)
}
fn subscriptions(&self, _model: &Model) -> Subscriptions<Msg> {
vec![]
}
fn update(&self, model: &mut Model, msg: Msg, quit_flag: &QuitFlag) -> Cmd<Msg> {
match msg {
Msg::Increment => {
model.counter += 1;
Cmd::None
}
Msg::Decrement => {
model.counter -= 1;
Cmd::None
}
Msg::Quit => {
quit_flag.raise();
Cmd::None
}
}
}
fn view(&self, frame: &mut Frame, model: &mut Model) {
let text = format!("Counter: {}", model.counter);
let paragraph = Paragraph::new(text).block(Block::default().borders(Borders::ALL));
frame.render_widget(paragraph, frame.area());
}
fn map_event(&self, _model: &Model, event: Event) -> Option<Msg> {
if let Event::Key(key) = event {
match key.code {
KeyCode::Char('+') => Some(Msg::Increment),
KeyCode::Char('-') => Some(Msg::Decrement),
KeyCode::Char('q') => Some(Msg::Quit),
_ => None,
}
} else {
None
}
}
}
With all this setup we're ready to run the application. The main function
will look like this:
fn main() -> std::io::Result<()> {
let app = MyApp {};
rustey::run(&app)?;
Ok(())
}
And with this we have a full implementation of a TUI counter application in less than 100 lines of code.
The main loop is initialized and runs like this:
flowchart TD
init_terminal["init terminal"]
init_app["get initial model and command"]
create_quitflag["set up quit flag"]
make_channel["set up message channel"]
start_subs["start subscriptions"]
main_loop["main event loop"]
start_cmd["start command if present"]
handle_cmd["handle command"]
draw_ui["draw UI"]
recv_msg["wait for message"]
update_model["update model"]
update_subs["update subscriptions"]
check_quit["check quit flag"]
break_loop["exit loop"]
restore_terminal["restore terminal"]
return_ok["finish"]
fetch_event["fetch event"]
map_event["map event"]
init_terminal --> init_app --> create_quitflag --> make_channel --> start_subs --> main_loop
main_loop --> start_cmd
main_loop --> draw_ui
start_cmd --> handle_cmd -- msg --> recv_msg
recv_msg --> update_model
update_model --> update_subs --> check_quit
check_quit -- "yes" --> break_loop --> restore_terminal --> return_ok
check_quit -- "no" --> main_loop
fetch_event -- "event" --> map_event -- "msg" --> recv_msg
An example application can be found at https://github.com/jacobat/rustey_list