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:
commit
6e77a81723
@ -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.
|
||||
|
||||
274
doc/guides/rust_tutorials/create_project.mdx
Normal file
274
doc/guides/rust_tutorials/create_project.mdx
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
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.
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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 |
BIN
doc/guides/rust_tutorials/img/create_project/02_makefile.png
Normal file
BIN
doc/guides/rust_tutorials/img/create_project/02_makefile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
BIN
doc/guides/rust_tutorials/img/create_project/03_lib.png
Normal file
BIN
doc/guides/rust_tutorials/img/create_project/03_lib.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
273
doc/guides/rust_tutorials/rust_in_riot.md
Normal file
273
doc/guides/rust_tutorials/rust_in_riot.md
Normal 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
|
||||
@ -73,6 +73,13 @@ export default defineConfig({
|
||||
"c_tutorials/saul",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Rust Basics",
|
||||
items: [
|
||||
"rust_tutorials/rust_in_riot",
|
||||
"rust_tutorials/create_project",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user