1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-14 17:13:50 +01:00

Merge pull request #21435 from AnnsAnns/tutorials/rust

Guides: Introduce Rust Guide
This commit is contained in:
crasbe 2025-07-21 20:37:45 +00:00 committed by GitHub
commit 6e77a81723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 556 additions and 255 deletions

View File

@ -1,258 +1,5 @@
Using Rust in RIOT {#using-rust}
==================
[TOC]
On supported CPUs, Rust can be used to develop RIOT applications.
Support is indicated in the `has_rust_target` feature,
and tested for in applications using the Makefile line
`FEATURES_REQUIRED += rust_target`.
In addition to the regular RIOT build toolchain
and a recent nightly Rust toolchain for the given target,
using this also requires C2Rust with some patches applied to be installed;
see <a href="#toolchain">toolchain</a> for installation instructions.
All these are readily available in the [official RIOT docker image],
which gets used by default if `BUILD_IN_DOCKER=1` is set.
[official RIOT docker image]: https://hub.docker.com/r/riot/riotbuild
Examples
--------
Two examples are provided:
* ``rust-hello-world`` is minimal in the sense of setup and code complexity; it is the typical Hello World example.
(Note that it is not necessarily minimal in terms of built size,
as Rust's regular printing infrastructure is more powerful and a bit heavier than your off-the-shelf ``printf``,
which embedded libcs already often trim down).
* ``rust-gcoap`` is a set of demo CoAP resources
both from the [coap-message-demos] crate (containing platform and library independent examples)
and from the [riot-module-examples] crate (containing RIOT specific examples).
There are [additional examples] available on GitLab,
maintained in coordination with the riot-wrappers crate.
[coap-message-demos]: https://gitlab.com/chrysn/coap-message-demos
[riot-module-examples]: https://gitlab.com/etonomy/riot-module-examples
[additional examples]: https://gitlab.com/etonomy/riot-examples/
IDE / editor setup
------------------
Users of Rust often take advantage of autocompletion or inline help.
To use this on RIOT projects,
some flags and environment variables have to be set,
which are listed by `make info-rust`.
These can be configured in the IDE's project setup
or exported as environment variables.
How it works
------------
The easy part of the story is that Rust code gets compiled into a static library
which is then linked together with the rest of the RIOT code;
if the main function happens to be implemented in Rust, so it is.
The **RIOT build system** contains rules and metadata to facilitate building and linking:
it calls `cargo` (Rust's own build system),
sets up paths to work well with out-of-tree builds,
configures the Rust target depending on the board's CPU,
and unpacks the static library into object files to facilitate interaction with XFA.
The [**riot-sys**] crate translates a selected subset of RIOT's header files for use in Rust;
this happens both using the [bindgen] crate (working from API information in header files)
and [C2Rust] \(translating static inline functions, and with some help from riot-sys, constant preprocessor initializers).
Functions exported by riot-sys are inherently unsafe to use (in Rust's sense of unsafe),
and may be somewhat volatile in their API due to mismatches between RIOT's / C's and Rust's API stability concepts.
The [**riot-wrappers**] crate creates safe and idiomatic wrappers around the types and functions provided by riot-sys.
Thanks to Rust's strong zero-cost abstractions, these often come at no increased runtime cost.
For example, locking a [riot_wrappers::mutex::Mutex] can rely on it having been properly initialized at creation;
furthermore, the mutex is freed when it goes out of scope.
Where practical, the wrappers are not accessed through own methods
but through established platform independent traits.
For example, the main API surface of an [I2CDevice]
is its implementation of the [corresponding embedded-hal I2C traits] for reading and writing.
The wrappers are [documented together with riot-sys and some of the examples].
[**riot-sys**]: https://crates.io/crates/riot-sys
[**riot-wrappers**]: https://crates.io/crates/riot-wrappers
[bindgen]: https://crates.io/crates/bindgen
[C2Rust]: https://c2rust.com/
[riot_wrappers::mutex::Mutex]: https://rustdoc.etonomy.org/riot_wrappers/mutex/struct.Mutex.html
[documented together with riot-sys and some of the examples]: https://rustdoc.etonomy.org/
[I2CDevice]: https://rustdoc.etonomy.org/riot_wrappers/i2c/struct.I2CDevice.html
[corresponding embedded-hal I2C traits]: https://rustdoc.etonomy.org/embedded_hal/blocking/i2c/index.html
Library components in Rust
--------------------------
It is possible to use Rust in different modules than the application itself.
Such modules are usually pseudomodules (although they may be mixed with C in regular modules as well).
They always depend on the `rust_riotmodules` module / crate,
which collects all enabled modules into a single crate by means of optional features.
If the application is not written in Rust,
that then depends on `rust_riotmodules_standalone`,
which adds a panic handler and serves as a root crate.
If the application is written in Rust,
`rust_riotmodules` needs to be added as a dependency of the application.
(This helps deduplicate between application and library code,
and also avoids symbol name clashes).
This is done by adding a dependency on the local `rust_riotmodules` crate (which is a no-op when no such modules are enabled),
and placing an `extern crate rust_riotmodules;` statement in the code.
(The latter is needed even after most `extern crate` was abolished in 2018,
because crates depended on but not used otherwise are usually not linked in).
Toolchain {#toolchain}
---------
To install the necessary Rust components, it is easiest use [**rustup**, installed as described on its website].
Using Rust on RIOT needs the latest stable version of Rust.
Make sure you have the core library for the CPU (**target**) of your choice available:
```
$ rustup target add thumbv7m-none-eabi
```
Substitute thumbv7m-none-eabi with the value of `RUST_TARGET`
in the output of `make info-build` of an application that has your current board selected
(or just add it later whenever the Rust compiler complains about not finding the core library for a given target).
Using the beta or nightly toolchains will work just as well
if they are selected through rustup's override mechanism.
While Rust comes with its own [cargo] dependency tracker for any Rust code,
it does not attempt to install **system components**.
To avoid playing the whack-a-mole of installing components whenever an install step fails,
consider installing this list of packages on Debian
(or an equivalent list on the distribution of your choice):
```
# apt install libclang-dev llvm llvm-dev cmake libssl-dev pkg-config
```
This encompass both components needed for riot-sys and for the later installation of C2Rust.
In addition to the Rust compiler you'll need to install the C2Rust transpiler;
as this is using some recent fixes, it is best installed as:
<!-- The locked works around <https://github.com/dtolnay/proc-macro2/issues/475> as closed in <https://github.com/immunant/c2rust/pull/1197>; there is no newer release that could be tested. -->
```shell
$ cargo install c2rust --git https://github.com/immunant/c2rust --tag v0.19.0 --locked
```
If multiple versions of LLVM are installed locally, it may be necessary to prefix it with the selected LLVM version:
```
$ LLVM_CONFIG_PATH=/usr/bin/llvm-config-16 cargo install …
```
[cargo]: https://doc.rust-lang.org/cargo/
[**rustup**, installed as described on its website]: https://rustup.rs/
Maintenance
-----------
The [riot-sys] and [riot-wrappers] crates are currently maintained as parts of the RIOT project.
While being released via crates.io on demand, usually RIOT uses a fixed version from the git repositories that are [easily updated].
The autogenerated bindings of the C API are slightly stricter than C's API,
and thus occasionally require additional work when C APIs change subtly.
The riot-sys crate takes the brunt of these changes --
it changes automatically, and no attempt is currently made to reflect these changes in a SemVer compatible manner.
The riot-wrappers crate smooths this out,
and provides an API that aims to be more stable than the C API.
It does that by generously converting types that changed,
and [introspecting generated bindings] or using [information provided by riot-sys].
The typical workflow of (C-nonbreaking, Rust-breaking) API changes is as follows:
* A PR subtly alters a type (eg. `uint8_t *` to `void *` in [#17990]).
Consequently, builds of Rust examples break.
* A PR is opened on riot-wrappers to smooth over the change, like [aab605f4] <!-- commit reference rather than PR as that was still on GitLab back then -->.
The PR is tested against current master in its CI (albeit not for the full set of boards).
To test whether it also works for the changed API,
a commit titled "REMOVEME Test with updated riot-wrappes" can be added to the original PR;
it alters `.cargo/config.toml` to point to the changed branch,
and removes any Cargo.lock files in the RIOT tree.
That PR is then merged.
* The version of riot-wrappers that works both with the previous and the new code
is pulled into the RIOT master branch by updating the Cargo.lock files.
The PR can look like [#18181], and verifies that the new riot-wrappers works on all boards.
That PR is then merged.
* For the next builds (up to the merging of) the original PR,
the REMOVEME commit can be removed.
It is good practice to rebase it onto the latest master after the update to riot-wrappers has been merged,
as this helps keeping bisectability up.
The PR now contains no Rust specific changes, and can be merged.
There are a few variations that can occur:
* Sometimes casting is not enough, and a type must be extracted from a signature.
[See the phydat callback type change] for an example.
* When neither casting nor type detection is sufficient,
a marker can be introduced through riot-sys;
it detects a keyword's presence in the source and passes it as [information provided by riot-sys] to riot-wrappers.
[See the coap_request_ctx_t change] for an example.
In that case, a riot-sys PR is opened in parallel to the riot-wrappers PR.
This method helps keeping changes backportable easily:
riot-sys and riot-wrappers are expected to work with the latest released version of RIOT all the time,
and avoid flag-day changes.
(Keeping riot-sys and riot-wrappers compatible with the latest release is also important to retain the ability to backport fixes).
* When functions are moved from being static and not being static,
their names go from `riot_sys::inline::name` to `riot_sys::name` (or vice versa).
riot-sys [has a list] of items that are always publicly exported directly as `riot_sys::name`;
just add the function there.
If non-generic types are referenced in them, they go from `riot_sys::inline::my_type_t` to `riot_sys::my_type_t`.
The [inline_cast] function family helps making that cast a bit safer.
* Things fail around atomics.
Until [C2Rust's support for atomics has improved],
riot-sys requires all exported headers to use the better supported `atomic_utils.h`.
If it is unavoidable that atomics are part of header files
(and not actually used in any static inline functions),
riot-sys's [atomics workarounds] can be extended as a last resort.
[riot-wrappers]: https://github.com/RIOT-OS/rust-riot-wrappers/
[riot-sys]: https://github.com/RIOT-OS/rust-riot-sys/
[easily updated]: https://github.com/RIOT-OS/RIOT/pull/17491#issuecomment-1143209437
[introspecting generated bindings]: https://github.com/RIOT-OS/rust-riot-wrappers/blob/db9d163e3eddcb7154edcf25db7207e4123964ee/src/helpers.rs#L3
[information provided by riot-sys]: https://github.com/RIOT-OS/rust-riot-sys/blob/525b2384a3541d4879a5f3845ee6241243c29a78/build.rs#L591
[#17990]: https://github.com/RIOT-OS/RIOT/pull/17990
[aab605f4]: https://github.com/RIOT-OS/rust-riot-wrappers/commit/aab605f464a279608ef0a8ad2afd5ae43179e330
[#18181]: https://github.com/RIOT-OS/RIOT/pull/18181
[See the phydat callback type change]: https://github.com/RIOT-OS/rust-riot-wrappers/pull/6/files#diff-ccb7946e3b4122ea3ce23fa9bc54eba63d75f7a6142fd4afdd9908b1bead50e0
[See the coap_request_ctx_t change]: https://github.com/RIOT-OS/rust-riot-wrappers/pull/4/files
[has a list]: https://github.com/RIOT-OS/rust-riot-sys/blob/525b2384a3541d4879a5f3845ee6241243c29a78/build.rs#L533
[inline_cast]: https://github.com/RIOT-OS/rust-riot-wrappers/blob/db9d163e3eddcb7154edcf25db7207e4123964ee/src/lib.rs#L68
[C2Rust's support for atomics has improved]: https://github.com/immunant/c2rust/issues/436
[atomics workarounds]: https://github.com/RIOT-OS/rust-riot-sys/blob/525b2384a3541d4879a5f3845ee6241243c29a78/riot-c2rust.h#L79
@deprecated Guides have moved to the [Guide Site](https://guide.riot-os.org/).
This page will be removed after release 2025.11.

View File

@ -0,0 +1,274 @@
---
title: "Creating a Project"
description: "Create a new project with a simple hello world program"
---
import GitSetup from '@components/gitsetup.mdx';
import Contact from '@components/contact.astro';
## Step 1: Create a new project
<GitSetup />
#### Open VS Code
Now that we have added RIOT as a submodule to our project,
we can start writing our hello world program.
You can use any text editor to create this file.
We will use Visual Studio Code in this example.
To open Visual Studio Code in the directory, you can use the following command:
```bash title="Open Visual Studio Code"
code .
```
## Step 2: Initialize Rust
Now that Visual Studio Code is open,
we need to tell Rust what kind of project we want to create.
We do this by running the following command in the terminal:
```bash title="Create a new Rust project"
cargo new hello_world --lib
```
This command creates a new project called `hello_world` with a library crate type.
The `--lib` flag tells Rust that we want to create a library crate instead of a binary crate.
A library crate is a collection of functions and types that can be used by other programs,
while a binary crate is an executable program.
RIOT then calls our `main` function when the program starts.
You should now have 3 new files within your project directory:
- `Cargo.toml`: This file contains metadata about your project and its dependencies.
- `src/lib.rs`: This file contains the source code for your library crate.
- `Cargo.lock`: This file contains information about the exact versions of your dependencies.
- This file is automatically generated by Cargo and should not be edited manually
so don't worry about it for now.
![The project structure](img/create_project/01_project_structure.png)
## Step 3: Creating the Makefile
Now that we have created our hello world program,
we need to create a Makefile to build our program.
The Makefile is a build automation tool
that allows us to define how our program should be built.
We create a new file called `Makefile` in the root directory of our project
and add the following code:
```makefile title="Makefile"
# name of your application
APPLICATION = hello-world
# The name of crate (as per Cargo.toml package name, but with '-' replaced with '_')
#
# The presence of this triggers building Rust code contained in this
# application in addition to any C code.
APPLICATION_RUST_MODULE = rust_hello_world
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# If you want to build in a Docker container, set this to 1.
# This is useful if you want to build on a system that does not have the
# RIOT toolchain installed, or if you want to ensure that the build is
# reproducible.
BUILD_IN_DOCKER ?= 1
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../../../..
# 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
include $(RIOTBASE)/Makefile.include
```
:::note
The `BUILD_IN_DOCKER=1` flag tells the build system to use the docker image
provided by RIOT to build our program.
This ensures that we have all the necessary dependencies to build our program.
If you have already built RIOT on your system,
you can omit this flag and the build system will use the toolchain installed on your system.
If you want to build your program on a different board,
you can change the `BOARD` variable to the name of the board you want to build for.
:::
Now RIOT knows that you want to build a Rust application
and will use the `hello_world` crate as the main module.
![The Makefile in Visual Studio Code](img/create_project/02_makefile.png)
## Step 3: Adjusting the Cargo.toml
Next, we need to adjust the `Cargo.toml` file to tell Cargo that we want to build for RIOT.
Open the `Cargo.toml` file and replace its contents with the following:
```toml title="Cargo.toml"
[package]
name = "hello-world"
version = "0.1.0"
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.1", features = [ "set_panic_handler", "panic_handler_format" ] }
rust_riotmodules = { path = "./RIOT/sys/rust_riotmodules/" }
```
The most important part here are the dependencies.
As mentioned in [Rust in Riot](/rust_tutorials/rust_in_riot/),
`riot-wrappers` is a crate that provides a set of wrappers around
RIOT's C functions to make them usable from Rust in a safe way.
The other configuration options are not that important for now,
but you can read more about them in the
[Cargo documentation](https://doc.rust-lang.org/cargo/reference/manifest.html).
### IDE Setup
Due to the way the RIOT Rust integration works, you need to amend your
normal Rust setup in your IDE to make it work with RIOT. Luckily RIOT
provides a small makefile command that will help you with that.
Run the following command in your terminal:
```bash
make info-rust
```
The output will look something like this:
```bash title="Output of make info-rust"
[ann@ann-laptop13 rust01-hello-world]$ make info-rust
cargo version
cargo 1.81.0 (2dbb1af80 2024-08-20)
c2rust --version
C2Rust 0.19.0
To use this setup of Rust in an IDE, add these command line arguments to the `cargo check` or `rust-analyzer`:
--profile release
and export these environment variables:
CARGO_BUILD_TARGET="thumbv7em-none-eabihf"
RIOT_COMPILE_COMMANDS_JSON="/home/ann/projects/exercises/rust01-hello-world/bin/feather-nrf52840-sense/cargo-compile-commands.json"
RIOTBUILD_CONFIG_HEADER_C="/home/ann/projects/exercises/rust01-hello-world/bin/feather-nrf52840-sense/riotbuild/riotbuild.h"
You can also call cargo related commands with `make cargo-command CARGO_COMMAND="cargo check"`.
Beware that the way command line arguments are passed in is not consistent across cargo commands, so adding `--profile release` or other flags from above as part of CARGO_COMMAND may be necessary.
```
Add the environment variables to your shell configuration file for this project
or use the `make cargo-command` command to run cargo commands with the correct arguments.
In VSCode you can now go to the workspace settings `.vscode/settings.json`
and add the following settings:
```json title=".vscode/settings.json"
{
"rust-analyzer.cargo.extraArgs": [
"--profile",
"release"
],
"rust-analyzer.cargo.target": "thumbv7em-none-eabihf",
}
```
## Step 4: Writing the Hello World Program
We are nearly done with setting up our project. The last thing we need to do is
to write the actual hello world program.
Open the `src/lib.rs` file and replace its contents with the following code:
```rust title="src/lib.rs"
#![no_std]
use riot_wrappers::riot_main;
use riot_wrappers::println;
extern crate rust_riotmodules;
```
`#![no_std]` tells the Rust compiler that we are building a program
that does not depend on the standard library,
which is not available on the hardware we are targeting.
This does mean that we can't use some of the standard library features
like `std::io` or `std::collections`, however,
there are a lot of `no_std` compatible crates available that provide similar functionality,
including `riot-wrappers` 😉
After that we import the functions that we will soon use to print to the console.
The `riot_main` macro helps RIOT figure out what our main function is
and `println` is a macro that we can use to print to the console,
this replaces the `println!` macro from the standard library,
since, as mentioned before, we can't use it on embedded systems.
Now we need to actually write the `main` function.
Add the following code to the `src/lib.rs` file:
```rust title="src/lib.rs"
riot_main!(main);
fn main() {
println!(
"You are running RIOT using Rust on a(n) {} board.",
riot_wrappers::BOARD
);
}
```
![The Full Code in VSCode](img/create_project/03_lib.png)
Congratulations! You have now created your first Rust program for RIOT. 🎉
## Step 5: Building the Program
<Contact />
To build our program, we use the following command:
```bash title="Build and flash the program"
make flash
```
After building the program,
we can run it using the following command to start the RIOT shell:
```bash title="Connect to the RIOT shell"
make term
```
You should see the following output:
```txt title="Output in the terminal"
You are running RIOT using Rust on a(n) native board.
```
![The Output in the Terminal](img/create_project/04_terminal_output.png)
## Conclusion
In this tutorial,
you have learned how to create a new Rust project for RIOT and how to build and run it.
You have also learned how to write a simple hello world program in Rust
and how to use the `riot-wrappers` crate to interact with RIOT's C functions.
:::note
The source code for this tutorial can be found
[HERE](https://github.com/RIOT-OS/RIOT/tree/master/examples/lang_support/official/rust-hello-world).
If your project is not working as expected,
you can compare your code with the code in this repository to see if you missed anything.
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,273 @@
---
title: Rust in RIOT
description: Using Rust in RIOT
---
:::tip
This document describes the general setup and usage of Rust in RIOT.
If you want to create a new application using Rust, you can start with the
[Creating a Rust application tutorial](/rust_tutorials/create_project/).
:::
On supported CPUs, Rust can be used to develop RIOT applications.
Support is indicated in the `has_rust_target` feature.
In addition to the regular RIOT build toolchain
and a recent nightly Rust toolchain for the given target,
using this also requires C2Rust with some patches applied to be installed,
see <a href="#toolchain">toolchain</a> for installation instructions.
All these are readily available in the [official RIOT docker image],
which gets used by default if `BUILD_IN_DOCKER=1` is set.
[official RIOT docker image]: https://hub.docker.com/r/riot/riotbuild
## Examples
Two examples are provided:
- `rust-hello-world` is minimal in the sense of setup and code complexity,
it is the typical Hello World example.
(Note that it is not necessarily minimal in terms of build size,
as Rust's regular printing infrastructure is more powerful and a bit heavier
than your off-the-shelf `printf`,
which embedded libcs already often trim down).
- `rust-gcoap` is a set of demo CoAP resources
both from the [coap-message-demos] crate (containing platform and library independent examples)
and from the [riot-module-examples] crate (containing RIOT specific examples).
There are [additional examples] available on GitLab,
maintained in coordination with the riot-wrappers crate.
[coap-message-demos]: https://gitlab.com/chrysn/coap-message-demos
[riot-module-examples]: https://gitlab.com/etonomy/riot-module-examples
[additional examples]: https://gitlab.com/etonomy/riot-examples/
## IDE / editor setup
Users of Rust often take advantage of autocompletion or inline help.
To use this on RIOT projects,
some flags and environment variables have to be set,
which are listed by `make info-rust`.
These can be configured in the IDE's project setup
or exported as environment variables.
## How it works
The easy part of the story is that Rust code gets compiled into a static library
which is then linked together with the rest of the RIOT code,
if the main function happens to be implemented in Rust, so it is.
The **RIOT build system** contains rules and metadata to facilitate building and linking:
it calls `cargo` (Rust's own build system),
sets up paths to work well with out-of-tree builds,
configures the Rust target depending on the board's CPU,
and unpacks the static library into object files to facilitate interaction with XFA.
The [**riot-sys**] crate translates a selected subset of RIOT's header files for use in Rust,
this happens both using the [bindgen] crate (working from API information in header files)
and [C2Rust] \(translating static inline functions, and with some help from riot-sys,
constant preprocessor initializers).
Functions exported by riot-sys are inherently unsafe to use (in Rust's sense of unsafe),
and may be somewhat volatile in their API due to mismatches between RIOT's / C's
and Rust's API stability concepts.
The [**riot-wrappers**] crate creates safe and idiomatic wrappers around the types and functions provided by riot-sys.
Thanks to Rust's strong zero-cost abstractions, these often come at no increased runtime cost.
For example, locking a [riot_wrappers::mutex::Mutex]
can rely on it having been properly initialized at creation,
furthermore, the mutex is freed when it goes out of scope.
Where practical, the wrappers are not accessed through own methods
but through established platform independent traits.
For example, the main API surface of an [I2CDevice]
is its implementation of the [corresponding embedded-hal I2C traits] for reading and writing.
The wrappers are [documented together with riot-sys and some of the examples].
[**riot-sys**]: https://crates.io/crates/riot-sys
[**riot-wrappers**]: https://crates.io/crates/riot-wrappers
[bindgen]: https://crates.io/crates/bindgen
[C2Rust]: https://c2rust.com/
[riot_wrappers::mutex::Mutex]: https://rustdoc.etonomy.org/riot_wrappers/mutex/struct.Mutex.html
[documented together with riot-sys and some of the examples]: https://rustdoc.etonomy.org/
[I2CDevice]: https://rustdoc.etonomy.org/riot_wrappers/i2c/struct.I2CDevice.html
[corresponding embedded-hal I2C traits]: https://rustdoc.etonomy.org/embedded_hal/blocking/i2c/index.html
## Library components in Rust
It is possible to use Rust in different modules than the application itself.
Such modules are usually pseudomodules
(although they may be mixed with C in regular modules as well).
They always depend on the `rust_riotmodules` module / crate,
which collects all enabled modules into a single crate by means of optional features.
If the application is not written in Rust,
that then depends on `rust_riotmodules_standalone`,
which adds a panic handler and serves as a root crate.
If the application is written in Rust,
`rust_riotmodules` needs to be added as a dependency of the application.
(This helps deduplicate between application and library code,
and also avoids symbol name clashes).
This is done by adding a dependency on the local `rust_riotmodules` crate
(which is a no-op when no such modules are enabled),
and placing an `extern crate rust_riotmodules,` statement in the code.
(The latter is needed even after most `extern crate` was abolished in 2018,
because crates depended on but not used otherwise are usually not linked in).
## Toolchain
Using Rust on RIOT needs the latest stable version of Rust. Please note that
many popular Linux distributions provide very old versions in their
repositories. Therefore to install the necessary Rust components, it is
recommended and easiest to use [**rustup**, installed as described
on its project website].
Make sure you have the core library for the CPU (**target**) of your choice available:
```shell
rustup target add thumbv7m-none-eabi
```
Substitute `thumbv7m-none-eabi` with the value of `RUST_TARGET`
in the output of `make info-build` of an application that has your current board selected
(or just add it later whenever the Rust compiler complains about not finding
the core library for a given target).
Using the beta or nightly toolchains will work just as well
if they are selected through rustup's override mechanism.
While Rust comes with its own [cargo] dependency tracker for any Rust code,
it does not attempt to install **system components**.
To avoid playing the whack-a-mole of installing components whenever an install step fails,
consider installing this list of packages on Debian
(or an equivalent list on the distribution of your choice):
```shell
apt install libclang-dev llvm llvm-dev cmake libssl-dev pkg-config
```
This encompasses both components needed for riot-sys and for the later installation of C2Rust.
In addition to the Rust compiler you'll need to install the C2Rust transpiler,
as this is using some recent fixes, it is best installed as:
<!-- The locked works around <https://github.com/dtolnay/proc-macro2/issues/475> as closed in <https://github.com/immunant/c2rust/pull/1197>, there is no newer release that could be tested. -->
```shell
cargo install c2rust --git https://github.com/immunant/c2rust --tag v0.19.0 --locked
```
If multiple versions of LLVM are installed locally,
it may be necessary to prefix it with the selected LLVM version:
```shell
LLVM_CONFIG_PATH=/usr/bin/llvm-config-16 cargo install …
```
[cargo]: https://doc.rust-lang.org/cargo/
[**rustup**, installed as described on its project website]: https://rustup.rs/
## Maintenance
The [riot-sys] and [riot-wrappers] crates are currently maintained as parts of the RIOT project.
While being released via crates.io on demand,
usually RIOT uses a fixed version from the git repositories that are [easily updated].
The autogenerated bindings of the C API are slightly stricter than C's API,
and thus occasionally require additional work when C APIs change subtly.
The riot-sys crate takes the brunt of these changes --
it changes automatically, and no attempt is currently made
to reflect these changes in a SemVer compatible manner.
The riot-wrappers crate smooths this out,
and provides an API that aims to be more stable than the C API.
It does that by generously converting types that changed,
and [introspecting generated bindings] or using [information provided by riot-sys].
The typical workflow of (C-nonbreaking, Rust-breaking) API changes is as follows:
- A PR subtly alters a type (eg. `uint8_t *` to `void *` in [#17990]).
Consequently, builds of Rust examples break.
<!-- commit reference rather than PR as that was still on GitLab back then -->
- A PR is opened on riot-wrappers to smooth over the change, like [aab605f4]
The PR is tested against current master in its CI (albeit not for the full set of boards).
To test whether it also works for the changed API,
a commit titled "REMOVEME Test with updated riot-wrappes" can be added to the original PR,
it alters `.cargo/config.toml` to point to the changed branch,
and removes any Cargo.lock files in the RIOT tree.
That PR is then merged.
- The version of riot-wrappers that works both with the previous and the new code
is pulled into the RIOT master branch by updating the Cargo.lock files.
The PR can look like [#18181], and verifies that the new riot-wrappers works on all boards.
That PR is then merged.
- For the next builds (up to the merging of) the original PR,
the REMOVEME commit can be removed.
It is good practice to rebase it onto the latest master after
the update to riot-wrappers has been merged,
as this helps keeping bisectability up.
The PR now contains no Rust specific changes, and can be merged.
There are a few variations that can occur:
- Sometimes casting is not enough, and a type must be extracted from a signature.
[See the phydat callback type change] for an example.
- When neither casting nor type detection is sufficient,
a marker can be introduced through riot-sys,
it detects a keyword's presence in the source and passes it as
[information provided by riot-sys] to riot-wrappers.
[See the coap_request_ctx_t change] for an example.
In that case, a riot-sys PR is opened in parallel to the riot-wrappers PR.
This method helps keeping changes backportable easily:
riot-sys and riot-wrappers are expected to work with the latest released
version of RIOT all the time,
and avoid flag-day changes.
(Keeping riot-sys and riot-wrappers compatible with the latest release
is also important to retain the ability to backport fixes).
- When functions are moved from being static and not being static,
their names go from `riot_sys::inline::name` to `riot_sys::name` (or vice versa).
riot-sys [has a list] of items that are always publicly exported directly as `riot_sys::name`,
just add the function there.
If non-generic types are referenced in them,
they go from `riot_sys::inline::my_type_t` to `riot_sys::my_type_t`.
The [inline_cast] function family helps making that cast a bit safer.
- Things fail around atomics.
Until [C2Rust's support for atomics has improved],
riot-sys requires all exported headers to use the better supported `atomic_utils.h`.
If it is unavoidable that atomics are part of header files
(and not actually used in any static inline functions),
riot-sys's [atomics workarounds] can be extended as a last resort.
[riot-wrappers]: https://github.com/RIOT-OS/rust-riot-wrappers/
[riot-sys]: https://github.com/RIOT-OS/rust-riot-sys/
[easily updated]: https://github.com/RIOT-OS/RIOT/pull/17491#issuecomment-1143209437
[introspecting generated bindings]: https://github.com/RIOT-OS/rust-riot-wrappers/blob/db9d163e3eddcb7154edcf25db7207e4123964ee/src/helpers.rs#L3
[information provided by riot-sys]: https://github.com/RIOT-OS/rust-riot-sys/blob/525b2384a3541d4879a5f3845ee6241243c29a78/build.rs#L591
[#17990]: https://github.com/RIOT-OS/RIOT/pull/17990
[aab605f4]: https://github.com/RIOT-OS/rust-riot-wrappers/commit/aab605f464a279608ef0a8ad2afd5ae43179e330
[#18181]: https://github.com/RIOT-OS/RIOT/pull/18181
[See the phydat callback type change]: https://github.com/RIOT-OS/rust-riot-wrappers/pull/6/files#diff-ccb7946e3b4122ea3ce23fa9bc54eba63d75f7a6142fd4afdd9908b1bead50e0
[See the coap_request_ctx_t change]: https://github.com/RIOT-OS/rust-riot-wrappers/pull/4/files
[has a list]: https://github.com/RIOT-OS/rust-riot-sys/blob/525b2384a3541d4879a5f3845ee6241243c29a78/build.rs#L533
[inline_cast]: https://github.com/RIOT-OS/rust-riot-wrappers/blob/db9d163e3eddcb7154edcf25db7207e4123964ee/src/lib.rs#L68
[C2Rust's support for atomics has improved]: https://github.com/immunant/c2rust/issues/436
[atomics workarounds]: https://github.com/RIOT-OS/rust-riot-sys/blob/525b2384a3541d4879a5f3845ee6241243c29a78/riot-c2rust.h#L79

View File

@ -73,6 +73,13 @@ export default defineConfig({
"c_tutorials/saul",
],
},
{
label: "Rust Basics",
items: [
"rust_tutorials/rust_in_riot",
"rust_tutorials/create_project",
],
},
],
},
{