Basic Example — Rust
Source: apps/rust/heap/example/src/lib.rs (also apps/rust/zeroheap/example/src/lib.rs) | WASM Demo
The Rust example demonstrates the ove crate with no_std support, Arc-shared kernel objects across threads, atomic counters, narrow Result-based error handling, core::time::Duration-typed timeouts, and compile-time RTOS detection. Heap mode and zero-heap mode share the same wrapper types — only the construction step differs.
Heap mode — Arc, Thread::builder, narrow errors
In heap mode each kernel object is allocated via Type::new() and wrapped in Arc<T> for sharing across threads. Threads spawn via Thread::builder() and take a FnOnce closure that captures Arc::clones by move. #[ove::main] is the proc-macro that emits the C-ABI ove_main entry symbol — the user-facing function stays plain Rust.
#![cfg_attr(not(feature = "std"), no_std)]
use core::sync::atomic::{AtomicU32, Ordering};
use core::time::Duration;
use ove::heap::Arc;
use ove::{InitCell, Priority, Queue, Thread, Timer};
// Pull in the libc-malloc-backed `#[global_allocator]` for bare-metal
// builds. On host (`feature = "std"`) it's a no-op.
use ove_allocator as _;
static UI_TIMER: InitCell<Timer> = InitCell::new();
static LAST_VALUE: AtomicU32 = AtomicU32::new(0);
#[ove::main]
fn app_main() {
ove::log::try_init();
log::info!("Rust example (heap mode): init");
// Heap-allocate the queue; Arc::clone hands each thread its own
// refcount. The kernel handle inside Queue<u32, 8> drops when the
// last Arc does.
let queue: Arc<Queue<u32, 8>> = Arc::new(Queue::new().expect("queue"));
let last_value: Arc<AtomicU32> = Arc::new(AtomicU32::new(0));
let q = Arc::clone(&queue);
let _producer = Thread::builder()
.name(c"producer")
.priority(Priority::Normal)
.stack_size(4096)
.spawn(move |_stop| {
let mut count: u32 = 0;
loop {
count += 1;
// try_send_for narrow error set: Timeout | QueueFull only.
match q.try_send_for(&count, Duration::from_millis(1000)) {
Ok(()) => {}
Err(ove::Error::Timeout) => log::warn!("send timeout"),
Err(ove::Error::QueueFull) => {
log::warn!("queue full, dropped {count}")
}
Err(_) => unreachable!(), // narrow set
}
Thread::sleep_ms(500);
}
})
.expect("producer spawn");
let q = Arc::clone(&queue);
let lv = Arc::clone(&last_value);
let _consumer = Thread::builder()
.name(c"consumer")
.priority(Priority::Normal)
.stack_size(4096)
.spawn(move |_stop| loop {
// Forever recv() is infallible — returns T directly.
if let Ok(v) = q.recv() {
lv.store(v, Ordering::Relaxed);
LAST_VALUE.store(v, Ordering::Relaxed);
if v % 5 == 0 {
log::info!("Consumer: count = {v}");
}
}
})
.expect("consumer spawn");
UI_TIMER.init(Timer::new(ui_timer_cb, 200, false).expect("timer"));
if lvgl::init().is_err() {
return;
}
{
let _g = lvgl::lock();
create_ui();
}
let _ = UI_TIMER.start();
ove::run();
}
What changes between heap and zero-heap
Same wrapper types, same method calls. Construction differs:
| Heap mode | Zero-heap mode | |
|---|---|---|
| Queue | Queue::<u32, 8>::new() |
ove::queue!(u32, 8) (expands to Queue::from_static(&mut S, ...)) |
| Mutex | Mutex::new(state) |
ove::mutex!(state) |
| Thread | Thread::builder().spawn(closure) |
same, plus optional ove::thread!(name, fn, prio, stack) for the bare-fn case |
| Sharing | Arc<T> clones |
ove::shared!(NAME: T) → InitCell<T> static |
| Global allocator | use ove_allocator as _; (libc-malloc shim) |
none — alloc not linked |
The zero-heap variant of this app lives at apps/rust/zeroheap/example/src/lib.rs. It uses ove::shared! cells instead of Arc, and ove::queue! / ove::thread! instead of Type::new():
#![cfg_attr(not(feature = "std"), no_std)]
use ove::{Priority, Queue, Thread};
ove::shared!(QUEUE: Queue<u32, 8>); // static QUEUE: InitCell<Queue<u32,8>>
fn producer_entry() {
let mut count: u32 = 0;
loop {
count += 1;
let _ = QUEUE.try_send_for(&count, core::time::Duration::from_millis(1000));
Thread::sleep_ms(500);
}
}
#[ove::main]
fn app_main() {
ove::log::try_init();
QUEUE.init(ove::queue!(u32, 8)); // → Queue::from_static(&mut S, ...)
let _ = ove::thread!("producer", producer_entry, Priority::Normal, 4096);
ove::run();
}
Compile-time RTOS detection
build.rs emits rtos_freertos, rtos_nuttx, rtos_zephyr, rtos_posix as cfg flags based on the substrate's ove_config.h:
#[cfg(rtos_freertos)]
const APP_TITLE: &[u8] = b"oveRTOS(FreeRTOS) Rust Demo\0";
#[cfg(rtos_nuttx)]
const APP_TITLE: &[u8] = b"oveRTOS(NuttX) Rust Demo\0";
#[cfg(rtos_zephyr)]
const APP_TITLE: &[u8] = b"oveRTOS(Zephyr) Rust Demo\0";
#[cfg(rtos_posix)]
const APP_TITLE: &[u8] = b"oveRTOS(POSIX) Rust Demo\0";
Logging
The standard log crate (log::info!, log::warn!, log::error!, log::debug!) routes through the oveRTOS console once ove::log::try_init() has been called. Any third-party crate using log:: macros logs through the same path.
ove::log::try_init();
log::info!("Producer started, queue at {:p}", &*queue);
Cooperative cancellation
The closure passed to Thread::builder().spawn(...) receives a StopToken. Outside code calls handle.request_stop() (returned from spawn); the worker observes stop.is_stopped() at its own polling points:
let handle = Thread::builder()
.name(c"worker")
.spawn(move |stop| {
while !stop.is_stopped() {
// do work…
Thread::sleep_ms(10);
}
log::info!("worker exiting cleanly");
})
.expect("spawn");
// later, from anywhere:
handle.request_stop();
Key APIs demonstrated
| Rust API | C Equivalent | Purpose |
|---|---|---|
Queue::<T, N>::new() |
ove_queue_create |
Typed fixed-size queue (heap) |
ove::queue!(T, N) |
ove_queue_init + storage |
Same, zero-heap-friendly macro |
Arc<Queue<...>> |
manual refcount | Shared kernel object across threads |
Thread::builder() |
ove_thread_create |
Closure-capturing spawn with stop-token |
Timer::new(cb, ms, oneshot) |
ove_timer_create |
Periodic callback timer |
InitCell<T> |
static initialization | OnceCell-style global cell |
#[ove::main] |
void ove_main(void) |
Export the C-ABI entry point |
ove::log::try_init() + log::info! |
OVE_LOG_INF |
std log crate integration |
AtomicU32 |
ove_mutex_* over int |
Lock-free shared counter |
Result<T, ove::Error> |
int rc |
Typed errors; narrow per-op sets |
core::time::Duration |
uint64_t timeout_ns |
Typed timeouts |
lvgl::init() / lvgl::lock() |
ove_lvgl_init |
LVGL bring-up + cross-thread mutex |
ove::run() |
ove_run |
Start scheduler |
How to build
make host.posix.example_rust # heap mode on POSIX
make host.posix.example_rust_zh # zero-heap mode on POSIX
make configure && make download && make && make run