Summary

Allow specifying "std" explicitly in Cargo.toml. Always provide "std" for every platform except those with #![no_core]. Make #![no_std] usage equal to using "std" with "default-features = false". And crates for embedded devices can use std with "default-features = false" now.

Motivation

Currently Rust has two different levels of standard library. "core" for embedded use cases, "std" for casual use cases. This effectively "split" rust eco-system into two very different worlds.

In this RFC I try to connect these two world into one, which gives smoother usage experience.

Guide-level explanation

Declaring std dependency in Cargo.toml is now optional. If it is not declared, it is equal to:

std = {builtin = true}

However when the crate is #![no_std], it is seen as:

std = {builtin = true, default-features = false}

Currently there will be two features declared: "alloc" and "full_std", both are enabled in default-features.

Without using any features, the APIs provided by libstd is exactly the same with libcore, so.

When using "alloc" only, the APIs provided by libstd will be those in the libcore plus those in liballoc.

When using "full_std" or "alloc"+"full_std", the APIs provided by libstd will be the same as today.

Reference-level explanation

"builtin" is an attribute that can and solely must be provided for the "std" dependency.

One possible temporary implementation is to provide different rlibs for each feature set of libstd .

In the long term "libstd" may be converted into a user compiled façade crate.

Drawbacks

Complexity in cargo logic.

Unresolved questions

What is the best approach for crate code detecting if "alloc" or "full_std" is currently enabled?

Future possibilities

In this RFC I try to connect these two world into one, which gives smoother usage experience.

As someone who does a lot of kernel development in Rust, I think this split is very important for reasoning about code in these contexts. For example, it’s nice to know that libcore will never allocate, and that something that depends on alloc might. For someone with a project that manually initialises dynamic memory allocation midway, the thought of having to check features of std on dependencies instead of just checking for alloc seems like a step backwards.

Alternative: allow use of libcore from non-no_std crates. This makes it easier to provide features disabling the parts of the crate that require libstd, so they can also be used from no_std contexts, but is less heavy-handed I feel.

This is how Edition 2018 works, core is always available unless no_core is specified and std is available unless no_std is specified. So a normal crate with no attribute can refer to either, and a crate with a std feature can simply use #![cfg_attr(not(feature = "std"), no_std)] and always use core rooted paths for the basic functionality.