最小の #![no_std] プログラム
この節では、コンパイルできる 最小の #![no_std] プログラムを書きます。
#![no_std] とは何か?
#![no_std] はクレートレベル属性であり、そのクレートが std クレートではなく core クレートにリンクすることを示します。では、これはアプリケーションにとって何を意味するのでしょうか。
std クレートは Rust の標準ライブラリです。これには、プログラムが directly on the metal ではなくオペレーティングシステム上で動作することを前提とした機能が含まれています。std はまた、そのオペレーティングシステムがサーバーやデスクトップで見られるような汎用 OS であることも前提としています。そのため、std は、そのような OS に通常備わっている機能、すなわちスレッド、ファイル、ソケット、ファイルシステム、プロセスなどに対する標準 API を提供します。
一方、core クレートは std クレートのサブセットであり、プログラムが実行されるシステムについて一切前提を置きません。そのため、浮動小数点数、文字列、スライスといった言語プリミティブの API に加え、アトミック操作や SIMD 命令のようなプロセッサ機能を扱う API も提供します。ただし、ヒープメモリ割り当てや I/O を伴うものの API はありません。
アプリケーションにとって、std は OS 抽象へのアクセス手段を提供する以上のことを行います。たとえば、スタックオーバーフロー保護の設定、コマンドライン引数の処理、プログラムの main 関数が呼び出される前のメインスレッドの生成などを担います。#![no_std] アプリケーションには、そうした標準ランタイムがありません。そのため、必要であれば自前のランタイムを初期化しなければなりません。
こうした性質のため、#![no_std] アプリケーションは、システム上で最初に、そして / または唯一実行されるコードになりえます。たとえば、標準的な Rust アプリケーションでは決してなれない次のようなものになれます。
- OS のカーネル。
- ファームウェア。
- ブートローダー。
コード
前置きはこのくらいにして、コンパイルできる最小の #![no_std] プログラムに進みましょう。
$ cargo new --edition 2024 --bin app
$ cd app
$ # main.rs を次の内容になるように変更する
$ cat src/main.rs
#![allow(unused)]
#![no_main]
#![no_std]
fn main() {
use core::panic::PanicInfo;
#[panic_handler]
#[inline(never)]
fn panic(_panic: &PanicInfo<'_>) -> ! {
loop {}
}
}
このプログラムには、標準的な Rust プログラムでは見かけないものがいくつか含まれています。
すでに詳しく扱った #![no_std] 属性です。
#![no_main] 属性は、このプログラムが標準の main 関数をエントリポイントとして使わないことを意味します。執筆時点では、Rust の main インターフェースはプログラムが実行される環境についていくつかの前提を置いています。たとえば、コマンドライン引数の存在を前提としているため、一般には #![no_std] プログラムには適していません。
#[panic_handler] 属性です。この属性が付いた関数は、ライブラリレベルの panic (core::panic!) と、言語レベルの panic(範囲外インデックスアクセス)の両方について、その動作を定義します。
このプログラムは有用なものを何も生成しません。実際、生成されるのは空のバイナリです。
$ # `size target/thumbv7m-none-eabi/debug/app` と同等
$ cargo size --target thumbv7m-none-eabi --bin app
text data bss dec hex filename
0 0 0 0 0 app
リンク前の時点では、このクレートには panic 処理のシンボルが含まれています。
$ cargo rustc --target thumbv7m-none-eabi -- --emit=obj
$ cargo nm -- -C $(pwd)/target/thumbv7m-none-eabi/debug/deps/app-*.o | grep '[0-9]* [^N] '
00000000 T __rustc::rust_begin_unwind
しかし、これは出発点です。次の節では、何か役に立つものを作ります。ただしその前に、Cargo を呼び出すたびに --target フラグを渡さなくて済むよう、デフォルトのビルドターゲットを設定しましょう。
$ mkdir .cargo
$ # .cargo/config.toml を次の内容になるように変更する
$ cat .cargo/config.toml
[build]
target = "thumbv7m-none-eabi"
eh_personality
設定が panic 時に無条件で abort しない場合、これは完全なオペレーティングシステム向けの大半のターゲットでそうなっていません(あるいは custom target に "panic-strategy": "abort" が含まれていない場合)、Cargo にそのように指示するか、eh_personality 関数を追加しなければなりません。後者には nightly コンパイラが必要です。これに関する Rust のドキュメントはこちらです。また、これに関する議論はこちらです。
Cargo.toml に次を追加します。
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
あるいは、eh_personality 関数を宣言します。アンワインド時に特別なことを何もしない単純な実装は次のとおりです。
#![allow(unused)]
#![feature(lang_items)]
fn main() {
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
}
これを含めない場合、language item required, but not found: 'eh_personality' というエラーが発生します。