diff --git a/examples/rust-async/Cargo.lock b/examples/rust-async/Cargo.lock new file mode 100644 index 0000000000..f2a3dc3b44 Binary files /dev/null and b/examples/rust-async/Cargo.lock differ diff --git a/examples/rust-async/Cargo.toml b/examples/rust-async/Cargo.toml new file mode 100644 index 0000000000..69d57736e0 --- /dev/null +++ b/examples/rust-async/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "rust-async" +version = "0.1.0" +authors = ["Christian Amsüss "] +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[profile.release] +# Setting the panic mode has little effect on the built code (as Rust on RIOT +# supports no unwinding), but setting it allows builds on native without using +# the nightly-only lang_items feature. +panic = "abort" + +[dependencies] +riot-wrappers = { version = "0.9", features = [ "set_panic_handler", "provide_critical_section_1_0", "panic_handler_format" ] } + +embassy-executor-riot = { git = "https://gitlab.com/etonomy/riot-module-examples" } +embassy-executor = "0.5" +embassy-futures = "0.1.1" +static_cell = "2.0.0" + +# While currently this example does not use any RIOT modules implemented in +# Rust, that may change; it is best practice for any RIOT application that has +# its own top-level Rust crate to include rust_riotmodules from inside +# RIOTBASE. +rust_riotmodules = { path = "../../sys/rust_riotmodules/" } diff --git a/examples/rust-async/Makefile b/examples/rust-async/Makefile new file mode 100644 index 0000000000..f782dac844 --- /dev/null +++ b/examples/rust-async/Makefile @@ -0,0 +1,30 @@ +# name of your application +APPLICATION = rust-async + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Some timers for the example +USEMODULE += ztimer_msec + +# Required by the async executor +USEMODULE += core_thread_flags + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +# The name of crate (as per Cargo.toml package name, but with '-' replaced with '_') +APPLICATION_RUST_MODULE = rust_async +BASELIBS += $(APPLICATION_RUST_MODULE).module + +FEATURES_REQUIRED += rust_target + +include $(RIOTBASE)/Makefile.include diff --git a/examples/rust-async/README.md b/examples/rust-async/README.md new file mode 100644 index 0000000000..222c368088 --- /dev/null +++ b/examples/rust-async/README.md @@ -0,0 +1,21 @@ +Rust: Asynchronous programming +============================== + +This is an example of how asynchronous Rust applications can be written on RIOT OS. + +The application starts an [embassy] based executor, +and then spawn several tasks on the main thread. +Unlike RIOT OS threads, these tasks can not preempt each other; +they relinquish control of the main thread when performing an asynchronous operation that would block, +and are paused by anything that preempts the main thread. + +Behind the scenes, the Rust compiler turns the asynchronous methods into state machines, +and the executor polls the state machines to make progress whenever an event wakes them up. + +Upsides of this style of programming are that there is less need for synchronization +(for individual tasks or their parts can rely on the work they do synchronously not to be executed concurrently with other tasks if so configured), +and that they can share a single stack space. +State that is persisted across await points is not stored on the stack, +but in adequately sized static memory for each task that can run. + +[embassy]: https://embassy.dev/ diff --git a/examples/rust-async/src/lib.rs b/examples/rust-async/src/lib.rs new file mode 100644 index 0000000000..41a9f409bc --- /dev/null +++ b/examples/rust-async/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright (C) 2020 Christian Amsüss +// +// This file is subject to the terms and conditions of the GNU Lesser +// General Public License v2.1. See the file LICENSE in the top level +// directory for more details. +#![no_std] + +use riot_wrappers::riot_main; +use riot_wrappers::println; +use riot_wrappers::ztimer::{Clock, Ticks}; + +use static_cell::StaticCell; + +extern crate rust_riotmodules; + +riot_main!(main); + +fn main() { + // Create an executor for running async functions + static EXECUTOR: StaticCell = StaticCell::new(); + let executor: &'static mut _ = EXECUTOR.init(embassy_executor_riot::Executor::new()); + executor.run(|spawner| { + spawner.spawn(amain(spawner)).expect("Starting task for the first time"); + }) +} + +#[embassy_executor::task] +async fn amain(spawner: embassy_executor::Spawner) { + println!("Running asynchronously:"); + + spawner.spawn(fast_messages("A")).expect("Starting task for the first time"); + spawner.spawn(fast_messages("B")).expect("Task is configured to allow two instances"); + + let msec = Clock::msec(); + + msec.sleep_async(Ticks(2500)).await; + + println!("Slept 2.5 seconds in 'amain' task, that should be enough."); +} + +#[embassy_executor::task(pool_size = 2)] +async fn fast_messages(taskname: &'static str) { + println!("Task {taskname} is running:"); + + let msec = Clock::msec(); + + msec.sleep_async(Ticks(1000)).await; + println!("Task {taskname} ticked"); + msec.sleep_async(Ticks(1000)).await; + println!("Task {taskname} tocked"); + msec.sleep_async(Ticks(1000)).await; + println!("Task {taskname} is done."); +}