改寫 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 的驗證方式#
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 庫的第二種方式(使用 #![cfg_attr (not (features = "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 環境下支持的。
具體的使用案例:
有些代碼也在 no_std 寫測試很難。因為這裡做了編譯選擇處理
一些 no_std 和 std 可以使用的 primitive 類型的倉庫#
引用及資源#
- 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.