| Crates.io | oxide-mvu |
| lib.rs | oxide-mvu |
| version | 0.4.2 |
| created_at | 2025-12-11 07:03:11.26465+00 |
| updated_at | 2026-01-25 12:29:43.623059+00 |
| description | A standalone MVU runtime for Rust with no_std support for embedded systems |
| homepage | https://github.com/noheltcj/oxide-mvu |
| repository | https://github.com/noheltcj/oxide-mvu |
| max_upload_size | |
| id | 1979285 |
| size | 109,753 |
A lightweight Model-View-Update (MVU) runtime for Rust with no_std support.
Note: This framework is under active development. APIs should not be considered stable.
The Model-View-Update pattern (also known as the Elm Architecture) structures applications as a pure functional loop:
(Event, Model) → (Model, Effect)This architecture eliminates implicit state mutation, making your application predictable, debuggable, and testable.
no_std support - Suitable for embedded systems (requires alloc)[dependencies]
oxide-mvu = "0.4.2"
For embedded systems (no_std):
[dependencies]
oxide-mvu = { version = "0.4.2", features = ["no_std"] }
use oxide_mvu::{Emitter, Effect, MvuLogic, MvuRuntime, Renderer};
// 1. Model the system, its behavior, and the renderable
// projection (Props) including any controls.
#[derive(Clone)]
enum Event {
Increment,
}
#[derive(Clone)]
struct Model {
count: i32,
}
struct Props {
count: i32,
on_click: Box<dyn Fn()>,
}
// 2. Implement application logic
struct Logic;
impl MvuLogic<Event, Model, Props> for Logic {
fn init(&self, model: Model) -> (Model, Effect<Event>) {
(model, Effect::none())
}
fn update(&self, event: Event, model: &Model) -> (Model, Effect<Event>) {
match event {
Event::Increment => (Model { count: model.count + 1 }, Effect::none()),
}
}
fn view(&self, model: &Model, emitter: &Emitter<Event>) -> Props {
let emitter = emitter.clone();
Props {
count: model.count,
on_click: Box::new(move || emitter.clone().try_emit(Event::Increment)),
}
}
}
// 3. Implement a renderer to display Props.
struct MyRenderer;
impl Renderer<Props> for MyRenderer {
fn render(&mut self, props: Props) {
println!("Count: {}", props.count);
}
}
// 4. Run the application
async fn main() {
let runtime = MvuRuntime::builder(
Model { count: 0 },
Logic {},
MyRenderer {},
|fut| { tokio::spawn(fut); },
).build();
runtime.run().await;
}
Working examples demonstrating oxide-mvu in various environments:
no_std embedded example running on
nRF52840 (Cortex-M4F) with Embassy async runtime, emulated via Renode.See the examples directory for more.
The MVU pattern makes applications easy to test. State transitions are pure functions:
#[test]
fn test_increment() {
let logic = Logic;
let model = Model { count: 0 };
let (new_model, _effect) = logic.update(Event::Increment, &model);
assert_eq!(new_model.count, 1);
}
For integration testing, use the testing feature:
[dev-dependencies]
oxide-mvu = { version = "0.4.2", features = ["testing"] }
This provides:
TestMvuRuntime - Runtime with manual event processing controlTestRenderer - Renderer that captures Props for assertionscreate_test_spawner() - Test-friendly task spawneruse oxide_mvu::{TestMvuRuntime, TestRenderer, create_test_spawner};
#[test]
fn test_full_mvu_loop() {
let renderer = TestRenderer::new();
let runtime = TestMvuRuntime::builder(
Model { count: 0 },
Logic {},
renderer.clone(),
create_test_spawner(),
).build();
let mut driver = runtime.run();
// Verify initial render
assert_eq!(renderer.count(), 1);
renderer.with_renders(|renders| {
assert_eq!(renders[0].count, 0);
});
// Trigger the Props callback and advance the runtime
renderer.with_renders(|renders| (renders[0].on_click)());
driver.process_events();
// Verify update
assert_eq!(renderer.count(), 2);
renderer.with_renders(|renders| {
assert_eq!(renders[1].count, 1);
});
}
See the tests directory for more examples.
Licensed under the Apache License, Version 2.0 (LICENSE or http://www.apache.org/licenses/LICENSE-2.0)