Rust no-std 工程实践

改写 std 的库为支持 no_std 的库及写出一个支持 std 和 no_std 库的经验谈 github repo: https://github.com/DaviRain-Su/rust-no-std-source

简介

首先介绍 std 和 no_std 的区别，然后介绍使用 no_std 库的方式，由于支持 no_std 的特性有两种不同的方式，因此使用 no_std 库也有两种方式。其次，验证一个库是否支持 no_std 特性的验证方式，如何改写一个 std 的库为同时支持 std 和 no_std 的特性方法。具体的如何写一个支持 std 和 no_std 的库。一些在 std 和 no_std 下都可以使用的 primitive 的仓库和相关的资源和文章。

目录

std 和 no_std 的区别

Rust 中使用 no_std 的两种方式

验证一个库是否支持 no_std 特性的验证方式

具体的写一个支持 std 和 no_std 的库

一些 no_std 和 std 可以使用 primitive 类型的仓库和相关资源的文章

std 和 no_std 的区别

核心库

Rust 语言的语法由核心库和标准库共同提供。 其中 Rust 核心库是标准库的基础。核心库中定义的是 Rust 语言的核心，不依赖于操作系统和网络等相关的库，甚至不知道堆分配，也不提供并发和 I/O

可以通过在模块顶部引入 #![no_std] 来使用核心库。核心库和标准库的功能有一些重复，包括如下部分：

基础的 trait，如 Copy、Debug、Display、Option 等。

基本原始类型，如 bool、char、i8/u8、i16/u16、i32/u32、i64/u64、isize/usize、f32/f64、str、array、slice、tuple、pointer 等。

常用功能型数据类型，满足常见的功能性需求，如 String、Vec、HashMap、Rc、Arc、Box 等。

常用的宏定义，如 println！、assert！、panic！、vec！等。做嵌入式应用开发的时候，核心库是必需的。

标准库

Rust 标准库提供应用程序开发所需要的基础和跨平台支持。标准库包含的内容大概如下：

与核心库一样的基本 trait、原始数据类型、功能型数据类型和常用宏等，以及与核心库几乎完全一致的 API。

并发、I/O 和运行时。例如线程模块、用于消息传递的通道类型、Sync trait 等并发模块，文件、TCP、UDP、管道、套接字等常见 I/O。

平台抽象。os 模块提供了许多与操作环境交互的基本功能，包括程序参数、环境变量和目录导航；路径模块封装了处理文件路径的平台特定规则。

底层操作接口，比如 std：：mem、std：：ptr、std：：intrinsics 等，操作内存、指针、调用编译器固有函数。

可选和错误处理类型 Option 和 Result，以及各种迭代器等。

还有一些解释，#![no_std] 是一个 crate level 级别的属性，表示 core crate 将链接到 core crate 而不是 std crate。

下面是 std crate 和 core crate 的解释，其实这里也就解释了标准库与和核心库之间的区别。当让也内在的包括了 std 与 no_std 之间的区别。

首先是，std crate 是 Rust 的标准库。它包含的功能假定程序将在操作系统上运行，而不是直接在裸系统上运行。std 还假定操作系统是一个通用的操作系统，就像人们在服务器和台式机上看到的那样。出于这个原因，std 为通常在这类操作系统中发现的功能提供了一个标准的 API: 线程、文件、套接字、文件系统、进程等等。

然后是，core crate 是 std crate 的一个子集，对程序运行的系统不做任何假设。因此它提供了基于语言的 API，如浮点，字符串和切片，以及暴露处理器特性的 API，如原子操作和 SIMD 指令。然而，它缺乏涉及堆内存分配和 I/O 的任何 API。

对于一个应用程序来说，std 所做的不仅仅是提供一种访问操作系统抽象的方式，std 还负责涉及堆栈溢出保护，处理命令行参数，以及在程序的主函数被调用之前生成主线程。一个#![no_std]应用程序缺乏所有这些标准的运行时，所以它必须初始化自己的运行时，如果需要的话。

由于这些特性，#![no_std]应用程序可以是第一个或者唯一在系统上运行的代码。

Rust 中 no_std 的一些使用方法

主要具体介绍第二种方式的使用 no_std

具体如何使用，参见写一个 no_std 的库的第二种使用方式。

也可参考，实例:serde no-std 的使用规范

验证一个库是否支持 no_std 的验证方式

Copy
cargo check --target wasm32-unknown-unknown

但是 wasm 环境不一定就是 no_std，或者别的编译目标也可以，也就是裸露的编译目标环境不带有任何系统的环境。

参考文档: 使用 Rust 编写操作系统（一）：独立式可执行程序

具体的写一个 no_std 的库

创建一个 no_std 库的第一种方式（使用#![no_std])

使用#![no_std]的话，默认的就是这个库是在 no_std 环境下的，然而又因为 no_std 下的库 一般来说都是核心库，而核心库又是标准库的子集，所以声明 #![no_std] 写出来的库，也可以在 std（标准库环境）下使用。

创建一个仓库

使用 #![no_std] 将这个仓库中的函数能支持在 no_std 和 std 下使用

开始添加一个函数编译报错 commit 1

修复错误 commit 2

创建 no_std 库的第二种方式（使用 #![cfg_attr (not (features = "std"), no_std)] )

创建一个仓库

使用#![cfg_attr(not(feature = "std"), no_std)]

添加的一些函数和测试

使一些不能在 no_std 环境下运行的仓库也能在 no_std 下支持

首先，要验证这个库能不能支持 no_std 的环境（见，验证一个库是否支持 no_std 的验证方式）。

找出这个库依赖的库支持 no_std 的方式，如果使用的是#![no_std] 那么这个库本身就是可以在 std 和 no_std 下同时的运行。

如果使用的是#![cfg_attr(not(features = "std"), no_std)], 就需要打开default-features = false, 进行配置。

最后可能需要做一些标准库的替换，使其能在 no_std 和 std 同时编译成功，一些可以使用的类型库有 sp-std (这个库仅仅封装了一部分的类型，例如有些类型是没有的，string,File, IO) 当然，IO,File，这些标准库在核心库当中是没有的。还有 rust 本身的 alloc, core 这些都是属于核心库的。也是在 no_std 环境下支持的。

具体的使用案例:

相关的 Pr, 使 ics23 支持 no_std

有些代码也在 no_std 写测试很难。因为这里做了编译选择处理

一些 no_std 和 std 可以使用的 primitive 类型的仓库

sp-std

rust Alloc crate

rust Core crate

引用及资源

Rust 编程之道核心库和标准库的介绍

Rust embeded book

扩展 no_std crate 的最佳实践

Rust API guidelines

Rust API guidelines Nameing

serde no_std 的使用规范

awesome-embedded-rust#no-std-crates

no standard library

serde 使用的第二种方式

Rust RFC Book no_std

Rust no_std DAQ

testing-for-no-std-compatibility

substrate 中关于 cfg_attr 的介绍

结论

参照 serder 的使用以及一些论坛的讨论，推荐使用#![cfg_attr(not(feature = "std"), no_std ))]来同时支持 std 和 no_std.