wg
wg copied to clipboard
List of `#![no_std]` issues
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.
- 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 onstd. If a dependency depends onstd(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 havestdin its sysroot (e.g. using Xargo). -
The current practice for providing features that depend on
stdin a crate is to provide them behind an opt-outstdCargo feature. This means thatno_stddevelopers have to do extra work to depend on such crate: they have to disable thestdfeature viadefault-features = falseplus re-enabling all the other default features. -
A lot of crates on crates.io are not compatible with
#![no_std]because they depend onstdeven 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_stdcrates 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 ifno_std-ness was checked and displayed on crates.io without human intervention.
- Supporting both
std,no_stdandno_std+allocis 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.
- Even if the
stdfacade 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.
- 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.
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.
- There are a few
no_stdforks of stuff that's provided bystd. 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.
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 = falseplus 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_stdcrates 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
corevsstdprelude 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.
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
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 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.
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)
A few more sharp-edges / missing primitives i have run into (some of which are mentioned above):
SystemTime,Instantetc. - https://internals.rust-lang.org/t/pre-rfc-move-systemtime-and-duration-to-libcore/3903CStr, (CStringthough this would presumably end up inalloc) - https://github.com/rust-lang/rust/issues/46736IpAddr,SocketAddretc. - https://github.com/rust-lang/rfcs/pull/2832