This is a question that I got stuck on a couple of month ago. My questions were answered in a disucssion with Shawn Tabrizi on GitHub, which was a big leap forward for me in understanding the architecture of substrate.
Why do we need this and in which cases would the
no_std attribute be applied?
Take a look at Architecture and the links therein. You'll find that a substrate node contains two version of the runtime code, the native runtime and the Wasm runtime (see https://en.wikipedia.org/wiki/WebAssembly). Note that the Wasm is stored on chain.
This is done to enable forkless runtime upgrades, in which the on-chain Wasm binary is replaced with a new version (but the native binary remains unchanged). Substrate uses Wasm as it is architecture independent (the Wasm must run on machines of different architecture, after all).
Now, whenever possible the faster native runtime is used to execute calls. But after a forkless runtime upgrade (in which only the Wasm updated), certain features of the native runtime may be outdated. Then the Wasm binary is used to execute the call, instead. See Executor for details.
Back to the
no_std attribute. When you build your node, the runtime/build.rs first builds the Wasm binary (see Build Scripts for details on build scripts) with the
no_std attribute. The reason for this is (again) architecture independence. Wasm cannot use the
libstd of Rust. The Embedded Rust Book puts it quite clearly:
In a bare metal environment no code has been loaded before your program. Without the software provided by an OS we can not load the standard library. Instead the program, along with the crates it uses, can only use the hardware (bare metal) to run.
In other words,
libstd is OS dependent, and that kind of thing doesn't fly with Wasm. Apparently you can use
libstd in Wasm, but this was rejected for a number of reasons: https://github.com/paritytech/substrate/issues/4043
So, after the Wasm binary is build, the runtime is compiled again, into the native binary, this time with
std enabled. The native binary actually contains the Wasm binary (we say that the Wasm is embedded into the native binary). This is done so that the first iteration of the Wasm binary can be copied from the native binary into the on-chain storage at genesis.
The inclusion of the Wasm is one example of where a block of code is excluded from the Wasm binary (for obvious reasons, in this case):
#[cfg(feature = "std")]
Other than that, it doesn't seem like many projects make much use of
std features directly, but in many of the standard dependencies you will frequently find
#[cfg(feature = "std")], for example frame/system. The idea is that using objects only available in
libstd will improve the performance of the native binary.
Why would we check if the
std feature is not being used if it is implicitly always used via the default feature?
All dependencies (expect for the Wasm builder, which isn't a part of the runtime) are included using
default-features = false, which disables this behavior (see Cargo > Features > The
default feature). For example: runtime/Cargo.toml.
I believe this is done for the following reason:
- The Wasm builder doesn't use the default feature
std. If you don't specify
default-features = false in the dependencies, then the dependencies use their
std feature, and then the Wasm can't be build.
- The "normal" build process (where you build the native binary) is done using the
std feature, and you don't want to keep typing
cargo build --features=std.
But to be honest, I'm a little shaky on this last point. Maybe someone else can confirm or clarify further.
To elaborate on point about the Wasm builder not using
std: When looking at the source of substrate_wasm_builder, you'll find that the builder loads the runtime
Cargo.toml and calls
wasm_project::create_and_compile (see https://crates.parity.io/src/substrate_wasm_builder/builder.rs.html#244), which in turn filters out the
std feature (https://crates.parity.io/src/substrate_wasm_builder/wasm_project.rs.html#314-340).