If you are seeing this, consider viewing presentation slides (online) instead. Or see README-NAVIGATE-SLIDES.md (online) for alternatives.
- general Rust developers moving to low level or
no_std
- non-Rust low level developers moving to Rust
Rust has #![no_std]
declaration, but it doesn't have #![std]
. Instead, if you don't have
#![no_std]
at the top of your crate, availability of std
library is implied.
Here we use std
to refer to either
- Rust
std
library, or - crates not declared as
no_std
(that is, crates that can use Ruststd
library).
- This applies to both
no_std
andstd
. - Rust can build for various targets (architectures/environments). But Cargo documentation keeps it
hidden in Cargo book's
FAQ
how to specify a target:
-
.cargo/config.toml
-->build.target
, for example:[build] target = "aarch64-unknown-none-softfloat"
-
- Architectures supported by Rust are in three
tiers. See also the rustc
book > Platform
Support. Beware many Tier 2
targets wouldn't build a simple (
no_std
) application - not even heapless. - The Cargo book > Platform specific dependencies
- Per-platform build/linking configuration: have
.cargo/config.toml
. - features: Compile
time-selectable subsets of library
crates
.
no_std
binaries- hardware, deployment, embedded debugging
- low level development in general (techniques, architectures, tools)
- specifics of real
time
applications and use with RTOS (Real Time OS)
- Rust is suitable for real time (because of no garbage collection & compilation)
- this applies to both
no_std
andstd
wasm
(Web Assembly), althoughno_std
crates are usually wasm-friendly- ABI (Application Binary Interface),
especially
#[no_mangle]
- type layout and the
Rustonomicon (Unsafe Rust) > Alternative
Representations.
- Rustonomicon has parts applicable to safe and/or
std
Rust, too. One of its gems: Higher-Rank Trait Bounds.
- Rustonomicon has parts applicable to safe and/or
async/await
in no_std- particular
no_std
-compatible crates
unsafe
code- seemingly easier in embedded, because of no threads & no other applications/processes. But that may lead to hidden bugs that show up only once using the same code in non-embedded later.
- FFI (Foreign Function Interface)/Interoperability
- Rust and Cargo & dependencies in general
- no need for low level experience
- common (and a few uncommon) aspects of general Rust, especially
- setting up a project and basics of cargo
- package layout
- dependencies
- features
- architecture or feature-based conditional
compilation with
#[cfg(...)]
attribute.
- experience with a statically typed and compiled language
- basics of linking, heap and stack
- Rust installation including
rustup
andcargo
(the recommended way) - Linux/Mac OS/Unix file path notation
A Rust no_std
crate can work with, or without, heap. Either way, it
- is for low level (without an operating system; or it serves as a part of an OS kernel)
- starts with a
#![no_std]
line at the top of your crate (lib.rs
or a top level source file for a binary). See also Rust glossary >package
and Rust glossary >target
. - if binary, it has no default fatal error
(
panic
) handler; you must either- set it to
abort
, or - use one of existing (embedded-friendly)
panic_handler
-defining crates
- set it to
- provide a custom
panic_handler
- if binary, and it uses heap, provide a global allocator
- has no access to
std
library - no module paths starting withstd::
, but - has a limited subset of
std
available ascore
andalloc
. For example:
- all data is on stack, or static
- common for microcontrollers
- no availability of
- only if you have an
allocator
-
register
#[global_allocator]
-
then use
alloc
library -
alloc
is equivalent to the respective subset ofstd
. Actually,std
re-exportsalloc
andcore
parts. -
alloc::boxed::Box
,alloc::vec::Vec
,vec!
macro (import it withuse alloc::vec;
),alloc::collections::VecDeque
,alloc::string::String
,alloc::rc::Rc
,alloc::sync::Arc
-
alloc::collections::BTreeSet
,alloc::collections::BTreeMap
if your items/keys implementcore::comp::Ord
.Even if our
struct
(orenum
) instances can't be ordered in human terms, or if the actual order doesn't matter for us, we could define some (predictable) order and useBTreeSet/BTreeMap
for most types.
-
Any no_std
code (whether heapless or with heap) is limited:
- no
std::collections::HashSet
, norstd::collections::HashMap
(since computing hashes needs a source of entropy). - no
std::thread::Thread
(and no multi-threading)
-
Embrace
nightly
channel (version) of Rust.no_std
development is challenging enough. Help yourself by new, often ergonomical, features of the language &core
library API (for example,#[bench]
). A lot ofnightly
API has become part ofbeta
andstable
(and anything new goes throughnightly
andstable
, anyway). See the Rust Forge schedule.Also, embedded devices often have no/restricted connectivity, and no other software running, so
nightly
may be secure enough. Plus, any upgrades replace the whole application, so even ifnightly
API changes, nothing outside of your embedded application changes (if the only change was in the Rustcore
or ABI). You you can go ahead and apply such changes bravely.If you need
beta
ornightly
, specify it per-project inrust-toolchain.toml
>[toolchain]
. See also nightly Rust, channels, Rustup overrides and Rustup profiles.Because of that, all Rust links here to Rust
core
/alloc
API, the Rust book and the Cargo book have anightly
prefix. The documentation clearly mentions which parts arenightly
(orbeta
) only, anyway. If you uwant to access it asbeta
then change thenightly
prefix tobeta
; or see thestable
by removing the prefix.See also the Rust RFC book.
Even if a library itself is no_std
, its unit tests (ones in modules marked with #[cfg(test)]
)
are in a separate crate (auto-generated by cargo test
). Hence the tests can use alloc
, and full
std
, too.
However, you'll need extern crate std;
in every test "top level" module (a module which has
[cfg(test)]
in front of its mod
keyword in its parent (non-test) module).
- No simple way to run/debug
no_std
binaries on desktop. - Workaround: Separate sets of crates:
*_build
for verification (onno_std
targets);*_test
for testing (on desktop).*_ok_std_*
,*_no_std_bare_*
(heapless) and*_no_std_heap_*
build with the relevant functionality.- Can't use a workspace for these alternatives (as all crates in a workspace share dependencies with same features).
- So, have the above builds in a separate crate each, under a directory like
test_crates/
. *_test
crates re-use (some of) the tests. Those are centralized under*_any_std_test_*
.- Suggest their folder names to start with a prefix based on/equal to the main crate. That makes navigation easier (in VS Code) when you open/compare... multiple main crates with similar-sounding sets of build & test crates.
- For an example see
ranging-rs/slicing-rs
>test_crates
:slicing_any_std_test
(tests are not run directly here, but are shared with the following*_test
crates)slicing_no_std_bare_build
slicing_no_std_bare_test
slicing_no_std_heap_build
slicing_no_std_heap_test
slicing_ok_std_test
Suggest not to specify channel = "nightly"
in rust-toolchain.toml
. Why? Occasionally some
targets are not available (on nightly
), unfortunately. To prevent that, look up the rustup
components history. Then in your
rust-toolchain.toml
> [toolchain]
have (for example) channel = "nightly-2022-08-27"
.
However, only numeric Rust versions qualify for the minimum supported Rust version - in your crate's
Cargo.toml > [package]
>
rust-version
.
If you need nightly
, you can run rustc --version
. Then specify its numeric version (excluding
-nightly
) in Cargo.toml > [package] > rust-version
. And put a timestamped channel = "nightly-YYYY-MM-DD"
in rust-toolchain.toml > [toolchain]
.
As of August 2022, these no_std
targets seem to build smoothly: aarch64-unknown-none-softfloat, aarch64-unknown-none, x86_64-unknown-none, riscv32i-unknown-none-elf, riscv32imac-unknown-none-elf, riscv32imc-unknown-none-elf, riscv64gc-unknown-none-elf, riscv64imac-unknown-none-elf
.
See
- no_std_data: Patterns on no_std data handling. Code is mostly done, but slides are work in progress.
- rust_incompatible_features: When
std
,no_std_bare
andno_std_heap
features (and potentially other features, as needed) benefit from being mutually exclusive/incompatible.
- Import
core::
andalloc::
(instead ofstd::
) wherever you can - even instd
development. That brings awareness about what parts of your library areno_std
-friendly.std
re-exportscore
andalloc
parts, so your library (whetherno_std
orstd
) will automatically work with crates that import the same symbols fromstd
.