banner
davirain

davirain

twitter
github
知乎
twitter

Rust no-std Engineering Practices

Rewrite the std library to support the no_std library and write an experience of supporting std and no_std libraries. GitHub repo: https://github.com/DaviRain-Su/rust-no-std-source

Introduction#

First, introduce the difference between std and no_std, and then introduce the way to use the no_std library. Since there are two different ways to support no_std features, there are also two ways to use the no_std library. Next, verify the way to verify if a library supports no_std features and how to rewrite a std library to support both std and no_std features. Specifically, how to write a library that supports both std and no_std. Some repositories, resources, and articles that can be used in both std and no_std.

Table of Contents#

  • Difference between std and no_std
  • Two ways to use no_std in Rust
  • Verification method for checking if a library supports no_std features
  • How to write a library that supports both std and no_std
  • Repositories, resources, and articles for using primitive types in both no_std and std

Difference between std and no_std#

Core Library#

The syntax of the Rust language is provided by the core library and the standard library. The Rust core library is the foundation of the standard library. The core library defines the core of the Rust language, independent of libraries related to operating systems and networks, and does not even know about heap allocation or provide concurrency and I/O.

The core library can be used by importing #![no_std] at the top of the module. The core library and the standard library have some overlapping functionality, including:

  • Basic traits such as Copy, Debug, Display, Option, etc.
  • Basic primitive types such as bool, char, i8/u8, i16/u16, i32/u32, i64/u64, isize/usize, f32/f64, str, array, slice, tuple, pointer, etc.
  • Common functional data types that meet common functional requirements, such as String, Vec, HashMap, Rc, Arc, Box, etc.
  • Common macro definitions such as println!, assert!, panic!, vec!, etc. The core library is necessary for embedded application development.

Standard Library#

The Rust standard library provides the foundation and cross-platform support for application development. The standard library includes:

  • Basic traits, primitive data types, functional data types, and common macros similar to the core library, as well as APIs almost identical to the core library.
  • Concurrency, I/O, and runtime. For example, thread modules, channel types for message passing, Sync trait, etc. for concurrency, and common I/O such as files, TCP, UDP, pipes, sockets, etc.
  • Platform abstraction. The os module provides basic functionality for interacting with the operating environment, including program parameters, environment variables, and directory navigation; the path module encapsulates platform-specific rules for handling file paths.
  • Low-level operation interfaces such as std::mem, std::ptr, std::intrinsics, etc., for manipulating memory, pointers, and calling compiler intrinsic functions.
  • Optional and error handling types such as Option and Result, as well as various iterators.

There is also an explanation that #![no_std] is a crate-level attribute that indicates that the core crate will be linked instead of the std crate.

The following is an explanation of the std crate and the core crate, which also explains the difference between the standard library and the core library. Of course, this also includes the difference between std and no_std.

First, the std crate is the standard library of Rust. It assumes that the program will run on an operating system rather than directly on bare metal. std also assumes that the operating system is a general-purpose operating system, similar to what people see on servers and desktops. For this reason, std provides a standard API for functionality typically found in such operating systems: threads, files, sockets, file systems, processes, etc.

Then, the core crate is a subset of the std crate and does not make any assumptions about the system on which the program runs. Therefore, it provides language-based APIs such as floats, strings, and slices, as well as APIs that expose processor features such as atomic operations and SIMD instructions. However, it lacks any APIs related to heap memory allocation and I/O.

For an application, std not only provides a way to access operating system abstractions, but also handles stack overflow protection, command line argument handling, and generates the main thread before the main function of the program is called. A #![no_std] application lacks all these standard runtimes, so it must initialize its own runtime if necessary.

Because of these features, a #![no_std] application can be the first or only code running on a system.

Some Usage Methods of no_std in Rust#

Mainly introduce the usage of the second method of using no_std.

For specific usage, see the second usage method of writing a no_std library.

Also refer to the example: Serde no-std usage specification

Verification Method for Checking if a Library Supports no_std Features#

cargo check --target wasm32-unknown-unknown

But the wasm environment is not necessarily no_std, and other compilation targets can also be used, that is, bare metal compilation targets without any system environment.

Reference document: Writing an Operating System in Rust (Part 1): Standalone Executables

Writing a no_std Library#

Create a no_std library using the first method (#![no_std])

If #![no_std] is used, the default is that the library is in the no_std environment. However, because libraries in the no_std environment are generally core libraries, and core libraries are subsets of the standard library, libraries declared with #![no_std] can also be used in std (standard library environment).

  1. Create a repository
  2. Use #![no_std] to make the functions in this repository support both no_std and std
  3. Start adding a function and fix compilation errors - commit 1
  4. Fix errors - commit 2

Create a no_std library using the second method (#![cfg_attr(not(features = "std"), no_std)])

  1. Create a repository
  2. Use #![cfg_attr(not(feature = "std"), no_std)]
  3. Add some functions and tests

Make libraries that cannot run in the no_std environment also support no_std.

First, verify whether the library can support the no_std environment (see the verification method for checking if a library supports no_std features).

Find out how the library dependencies support no_std. If #![no_std] is used, the library itself can run in both std and no_std environments.

If #![cfg_attr(not(feature = "std"), no_std)] is used, default-features = false needs to be opened for configuration.

Finally, some standard library replacements may be needed to compile successfully in both no_std and std environments. Some available type libraries are sp-std (which only encapsulates some types, for example, some types are not available, such as string, File, IO). Of course, IO and File are not available in the core library. There are also rust's own alloc and core, which belong to the core library and also support the no_std environment.

Specific usage examples:

Related PR to support no_std in ics23

Some code is difficult to test in the no_std environment. This code handles compilation selection

Repositories, Resources, and Articles for Using Primitive Types in both no_std and std#

References and Resources#

Conclusion#

Referring to the usage of Serde and discussions on forums, it is recommended to use #![cfg_attr(not(feature = "std"), no_std))] to support both std and no_std.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.