wg icon indicating copy to clipboard operation
wg copied to clipboard

List of `#![no_std]` issues

Open japaric opened this issue 7 years ago • 6 comments

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

japaric avatar Mar 15 '18 01:03 japaric

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.

Ericson2314 avatar Mar 15 '18 01:03 Ericson2314

As a concrete example, I wanted a Duration type in my measurements crate but it's in std, not core. I tried the time crate, but that depends on libc which isn't no_std unless you give it a flag. It compiles and tests pass on my PC, but it doesn't build for ARM with Xargo.

On 15 Mar 2018 01:53, "John Ericson" [email protected] wrote:

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.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/rust-lang-nursery/embedded-wg/issues/64#issuecomment-373233847, or mute the thread https://github.com/notifications/unsubscribe-auth/AA6lj0mQGAvtNj_jOrpa5Itg86NL2Yrwks5tecmigaJpZM4SrZjv .

thejpster avatar Mar 15 '18 08:03 thejpster

@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

japaric avatar Mar 15 '18 08:03 japaric

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

therealprof avatar Mar 15 '18 09:03 therealprof

Also there's this cargo limitation: https://github.com/rust-lang/cargo/issues/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 https://github.com/bluss/either/issues/23 https://users.rust-lang.org/t/cargo-features-for-host-vs-target-no-std/16911)

dcarosone avatar Apr 19 '18 07:04 dcarosone

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

  • SystemTime, Instant etc. - https://internals.rust-lang.org/t/pre-rfc-move-systemtime-and-duration-to-libcore/3903
  • CStr, (CString though this would presumably end up in alloc) - https://github.com/rust-lang/rust/issues/46736
  • IpAddr, SocketAddr etc. - https://github.com/rust-lang/rfcs/pull/2832

ryankurte avatar Aug 01 '20 08:08 ryankurte