1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

examples: Add asynchronous Rust example

This commit is contained in:
chrysn 2024-08-23 01:59:20 +02:00
parent b802a19059
commit 7a348aebde
5 changed files with 132 additions and 0 deletions

BIN
examples/rust-async/Cargo.lock generated Normal file

Binary file not shown.

View File

@ -0,0 +1,28 @@
[package]
name = "rust-async"
version = "0.1.0"
authors = ["Christian Amsüss <chrysn@fsfe.org>"]
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/" }

View File

@ -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

View File

@ -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/

View File

@ -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<embassy_executor_riot::Executor> = 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.");
}