Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List of #![no_std] issues #64

Open
japaric opened this issue Mar 15, 2018 · 6 comments
Open

List of #![no_std] issues #64

japaric opened this issue Mar 15, 2018 · 6 comments
Labels
survey triage-2024-keep Issues triaged in 2024 and considered OK to keep open upstream

Comments

@japaric
Copy link
Member

japaric commented Mar 15, 2018

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.

@Ericson2314
Copy link

Ericson2314 commented Mar 15, 2018

I'm pro-facade. With, that, the solution looks like having these libraries always be !#[no_std] and just optimally extern alloc or std. After that, the main ergonomics problem is prelude management.

Adding #![no_std] to a crate doesn't mean it doesn't depend on std...

Cargo's [patch] should be able to remove a create, just as it can add more or override ones. Then with stdlib-aware cargo we can ban std from a workspace root.

They have to disable the std feature via default-features = false plus re-enabling all the other default features.

There is a general problem in Cargo.toml of wanting do to "I'll depend on this library only if something else needs it too". std should ideally not be a special case with whatever is the best the solution.

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.

Eventually I could see the portability lint discovering this and providing a hint, but first I think we should just focusing on making no-std libraries no harder to maintain so people when prodded switch. [If we could only get rid of std and just have creates beneath it, that would also help greatly, but alas we cannot. Good thing the portability-lint-suggestion solution is equivalent though much more work to implement.]

It's hard to find no_std crates on crates.io...

We should be able to query via dependencies. std should be just another dependency.

...Just having to deal with the
differences in the core vs std prelude is a lot of work...

That is definitely one of the hardest parts in the short term. Custom preludes or similar is the best I can think of.

@thejpster
Copy link
Contributor

thejpster commented Mar 15, 2018 via email

@japaric
Copy link
Member Author

japaric commented Mar 15, 2018

@thejpster

I wanted a Duration type in my measurements crate but it's in std, not core.

core::time::Duration. I don't know since when it's been there but since it's unstable (at least until 1.25 is released) it doesn't show up in https://doc.rust-lang.org

@therealprof
Copy link
Contributor

@japaric Do the problems with compile time sized stack objects count? I hope (but am not sure) that const generics will be sufficient to address the problem that it is currently very unergonomic and cumbersome (via Unsized hacks) to reserve some (at compile time) known amount of space in objects. Not to mention the somewhat costly runtime bound checks.

In my humble opinion this could eliminate a lot of alloc dependencies and make more crates no_std-able. Often people don't know (or don't want to wrap their head around some complicated workaround) how to allocate a static amount of memory for their structures on the stack based on parameters or generics.

@dcarosone
Copy link

Also there's this cargo limitation: rust-lang/cargo#2589

Cargo feature resolving for dependencies is done across both build-deps (native target) as well as code deps (embedded target) in a single pass. This means you basically can't use crates that are used by proc-macro or other compiler plugins (the case I hit was either rayon-rs/either#23 https://users.rust-lang.org/t/cargo-features-for-host-vs-target-no-std/16911)

@ryankurte
Copy link
Contributor

A few more sharp-edges / missing primitives i have run into (some of which are mentioned above):

@adamgreig adamgreig added the triage-2024-keep Issues triaged in 2024 and considered OK to keep open label Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
survey triage-2024-keep Issues triaged in 2024 and considered OK to keep open upstream
Projects
None yet
Development

No branches or pull requests

7 participants