14

This attribute is applied to the item it is defined in and conditionally applies the no_std attribute if we are not using the "std" feature. Why do we need this and in which cases would the no_std attribute be applied?

My current understanding is that the default feature of a crate is always used, for a pallet, the default feature is made up of the std feature. Why would we check if the std feature is not being used if it is implicitly always used via the default feature?

I asked a similar question here std in conditional compilation but this one is more suitable, can the other be deleted or closed?

2 Answers 2

13

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")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));

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.

Gory Details

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

3
  • 2
    Actually wasm has support for rust-std, see here for the choice behind not supporting std when compiling to wasm github.com/paritytech/substrate/issues/4043. Also i looked through builder.rs, i cannot find the part where the "--no-default-features" flag is passed so that the no_std attibute is applied, can anyone show me this in the code, or perhaps another method is used? Commented Mar 28, 2022 at 14:17
  • 2
    @infinitesimallySmart Thanks for the correction! I've updated the answer. Does that answer your question?
    – mkl
    Commented Mar 29, 2022 at 0:16
  • Yes, Excellent answer Commented Mar 29, 2022 at 10:40
3

Answer to this question lies in how the runtime get compiled found here. Runtime get compiled to both native binary as well as a wasm module.

Some features like System Libraries, File I/O, Networking etc doesn't work well with WebAssembly for now. So we make sure to not use them. And hence we stick to explicitly avoid use of std lib.

The following porting guide will also shed light and make things more clear

https://udoprog.github.io/rust/2018-02-19/porting-rust-to-wasm.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.