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, there are also two ways to use the no_std library. Next, verify the way to verify if a library supports the no_std feature 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 and related 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
  • How to write a library that supports both std and no_std
  • Repositories and resources 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, which does not depend on libraries related to operating systems and networks, and does not even know about heap allocation, 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 needed 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 file, TCP, UDP, pipe, socket, 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, etc.

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, as seen 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 makes no 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 involving 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 parameter handling, and generates the main thread before the main function of the program is called. A #![no_std] application lacks all these standard runtime features, 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#

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 Executable

Writing a no_std Library#

Create a no_std library using the first method (using #![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 the compilation error commit 1
  4. Fix the error commit 2

Create a no_std library using the second method (using #![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

Enable some libraries that cannot run in the no_std environment to support no_std

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

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 include sp-std (which only wraps some types, such as string, File, IO), IO, File, which are not available in the core library. In addition, Rust's own alloc, core, etc. belong to the core library and are also supported in 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 handles the compilation selection

Repositories and Resources 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.