Skip to content

List of #![no_std] issues  #64

Open
@japaric

Description

@japaric

This is a list issues related to #![no_std] that embedded developers tend to encounter in
practice. The main goal of this ticket is to make the portability WG aware of these issues.

  1. There are a lot of ergonomics issues and papercuts related to the use of #![no_std]. Addressing ergonomics around Rust and embedded/no_std development #26 goes
    into detail but to summarize the main problems:
  • Adding #![no_std] to a crate doesn't mean it doesn't depend on std. If a dependency depends on
    std (i.e. it's not #![no_std]) then the top crates does as well. The only way to be really
    sure is to compile the crate for a target that doesn't have std in its sysroot (e.g. using
    Xargo).

  • The current practice for providing features that depend on std in a crate is to provide them
    behind an opt-out std Cargo feature. This means that no_std developers have to do extra work
    to depend on such crate: they have to disable the std feature via default-features = false
    plus re-enabling all the other default features.

  • A lot of crates on crates.io are not compatible with #![no_std] because they depend on std
    even though they don't necessarily need to, e.g. the author just forgot to add #![no_std] to the
    crate. This section of my embedded Rust in 2018 blog post goes into more detail
    about the problem.

  • It's hard to find no_std crates on crates.io. There's a no-std category on crates.
    io but not everyone uses it, or is aware of it (see previous bullet). It would be better if
    no_std-ness was checked and displayed on crates.io without human intervention.
  • Supporting both std, no_std and no_std+alloc is a tough job. Just having to deal with the
    differences in the core vs std prelude is a lot of work. For example, see the manually crafted
    prelude
    that the serde project is using.

All these issues might disappear or be fixed with the elimination of the std facade and
#![no_std], or they might not. I do not know.

  1. Even if the std facade and #![no_std] are gone it's pretty important that libraries support a
    no dynamic memory allocation mode. Microcontrollers are resource constrained devices and
    sometimes a memory allocator is too heavyweight a dependency; also there application spaces (e.g.
    safety critical) where dynamic memory allocation is downright banned (cf. MISRA standard).

It's already hard to figure out whether something allocates or not. #![no_std] and the lack of
extern crate alloc is a good indicator that a crates doesn't allocate. Not compiling the alloc
crate as part of the Xargo sysroot is a sure way to exclude the memory allocator. I'm afraid it may
become impossible to tell whether a dependency allocates if #![no_std] and the std facade are
gone.

I don't know if the portability lint could help with this (#![deny(alloc)]?) or if we should have
a guideline about making a no dynamic memory allocation mode available via a Cargo feature then you
can simply look for the presence of such Cargo feature, but I guess it would be hard to make sure
everyone follows the guideline.

  1. Math support in no_std.

sqrt, sin and friends are not available in no_std programs. In std these functions come
from the system libm.a, which is a C dependency. It's not convenient to use a C implementation in
no_std because it requires you to get a full C toolchain (whereas normally you only need a linker)
that contains libm.a (assuming there's a pre-compiled libm.a available for your system) and
you'll likely will have to tweak the linker invocation to link the right libm.a to your program
(e.g. the ARM Cortex-M toolchain ships with like 4 different libm.a binaries compiled using
different profiles).

There are pure Rust implementations of libm like the m crate and math-rs which are more
convenient to use as you don't need a full C toolchain or mess with the linking process. If the
std facade is to be eliminated there should be some mechanism to be able to use such crate instead
of the C libm.a that the std crate pulls in.

Another alternative would be to have a rust-lang's Rust implementation of libm but that's a lot
of work. Though such implementation could be done incrementally by compiling the functions not yet
implemented in Rust from some C code base (e.g. Julia's openlibm) which is the approach we are using
for compiler_builtins.

  1. compiler_builtins.

In no_std programs you have to explicitly link to the compiler_builtins crate or at least one of
your dependencies has to. The std crate depends on compiler_builtins so you don't need a
explicit extern crate compiler_builtins in std programs.

compiler_builtins is an implementation detail of the LLVM backend and no Rust user should ever
need to deal with it. Ideally if you link to core then compiler_builtins should also be linked
in but it's complicated.

The LLVM interface demands that the compiler intrinsics in compiler_builtins are linked in as a
separate object file and that such object file is passed as the last object file argument to the
linker. The compiler_builtins crate is marked with a special attribute (#![compiler_builtins],
iirc) so that this holds true even in the presence of LTO (where you would expect a single object
file to be produced -- due to compiler_builtins you end with two object files).

Furthermore compiler_builtins depends on core so you can't make core depend on
compiler_builtins; also the core crate isn't suppose to have any dependency. The separate object
file requirement also means that compiler_builtins can't be merged into core. This means that
even if you don't need anything other than core you still have to depend on the forever unstable
compiler_builtins crate to build a no_std binary.

I don't know what are the plans of the portability WG wrt to the compiler_builtins crate (it can't
be merged into std for the same reason it can't be merged into core) but from our point of view
it needs to disappear (*) (easier said than done) as it ties development of no_std binaries to
the nightly channel.

(*) iirc, @eddyb had some ideas to put the compiler intrinsics in core while preserving the
requirement of a separate object file but I don't remember the details.

  1. There are a few no_std forks of stuff that's provided by std. For example, cty,
    cstr_core, hashmap_core, etc. These should be provided by rust-lang to avoid code
    duplication and bitrot (of the forks).

cc @jethrogb rust-lang-nursery/portability-wg#9

Community, if there's anything you think is missing from this list feel free to leave a comment
below and I'll add it to the list.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions