改写 std のライブラリを no_std をサポートするライブラリにし、std と no_std の両方をサポートするライブラリの経験談を記述した github リポジトリ: https://github.com/DaviRain-Su/rust-no-std-source
概要#
まず、std と no_std の違いを紹介し、次に no_std ライブラリの使用方法を説明します。no_std をサポートする特性には 2 つの異なる方法があるため、no_std ライブラリの使用方法も 2 つあります。次に、ライブラリが no_std 特性をサポートしているかどうかを検証する方法、std ライブラリを no_std と std の両方をサポートするように改写する方法について説明します。具体的には、std と no_std をサポートするライブラリの作成方法、一部の std と no_std の両方で使用できるプリミティブのリポジトリ、関連リソースや記事についても触れます。
目次#
- std と no_std の違い
- Rust における no_std の 2 つの使用方法
- ライブラリが no_std 特性をサポートしているかどうかの検証方法
- std と no_std をサポートするライブラリの具体的な作成方法
- no_std と std で使用できるプリミティブ型のリポジトリと関連リソースの記事
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 モジュールは、プログラムパラメータ、環境変数、ディレクトリナビゲーションなど、操作環境と相互作用する基本機能を提供します;path モジュールは、ファイルパスを処理するプラットフォーム固有のルールをカプセル化しています。
- 低レベル操作インターフェース(std::mem、std::ptr、std::intrinsics など)、メモリ、ポインタ、コンパイラ固有関数の呼び出しを操作します。
- オプションとエラー処理型(Option と Result)、およびさまざまなイテレータなど。
さらに説明すると、#![no_std]
は crate レベルの属性であり、core crate が std crate ではなく core crate にリンクされることを示します。
以下は std crate と core crate の説明であり、実際には標準ライブラリとコアライブラリの違いを説明しています。また、std と no_std の違いも内在的に含まれています。
まず、std crate は Rust の標準ライブラリです。これに含まれる機能は、プログラムがオペレーティングシステム上で実行されることを前提としています。std はまた、オペレーティングシステムが一般的なものであると仮定しています。これは、サーバーやデスクトップで見られるようなものです。このため、std は通常このようなオペレーティングシステムで見られる機能に対して標準 API を提供します:スレッド、ファイル、ソケット、ファイルシステム、プロセスなど。
次に、core crate は std crate のサブセットであり、プログラムが実行されるシステムについて何の仮定も行いません。したがって、浮動小数点、文字列、スライスなどの言語に基づく API や、原子操作や SIMD 命令などのプロセッサ特性を扱う API を提供します。しかし、ヒープメモリ割り当てや 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] を使用してこのリポジトリの関数が no_std と std の両方で使用できるようにする
- 関数を追加してコンパイルエラーを発生させるコミット 1
- エラーを修正するコミット 2
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 で使用できるプリミティブ型のリポジトリ#
引用およびリソース#
- Rust プログラミングの道コアライブラリと標準ライブラリの紹介
- Rust 埋め込みブック
- no_std crate の拡張に関するベストプラクティス
- Rust API ガイドライン
- Rust API ガイドラインの命名
- 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 の紹介
結論#
serde の使用やいくつかのフォーラムの議論を参考にして、#![cfg_attr(not(feature = "std"), no_std)]
を使用して std と no_std の両方をサポートすることを推奨します。