ランタイム
最初の依存関係である cortex-m-rt から始めましょう。このクレートは、Cortex-M マイクロコントローラー向けのスタートアップコードと最小限のランタイムを提供します。すでにご存じかもしれませんが、ここで使う micro:bit も Cortex-M コアをベースにしています。
なぜ必要なのでしょうか?
組み込み開発では、通常、その下にある OS は存在しません(ただし、マイクロコントローラー向けの専用オペレーティングシステムは存在します)。つまり、プログラムをどのように開始するか、メモリをどのように初期化するか、ボタンの押下やデータの受信のようなイベントにデバイスがどのように応答するかなど、すべてを自分で設定しなければならないということです。
これらすべてを機能させるために、ランタイムクレートを使います。組み込み Rust におけるランタイムは、main 関数の前に実行される最小限のスタートアップコードを提供し、メモリ(スタックやヒープなど)をセットアップし、プログラムが割り込みにどう反応すべきかを定義するのを助けます。
エントリーポイント
開発者の視点では、プログラムの実行時に最初に実行されるコードは main 関数のように思えるかもしれません。しかし、実際にはそうではありません。ほとんどの言語では、最終的に main を呼び出す前にランタイムシステムが環境をセットアップします。
それに対して、micro:bit のような組み込みシステムには標準のランタイムがありません。その代わりに、cortex-m-rt クレートが提供するもののようなカスタムランタイムを使います。この構成では、プログラムのエントリーポイントを明示的に指定する必要があります。micro:bit v2 では、これは cortex-m-rt が提供する #[entry] 属性 を使って行い、これによって最初に実行する関数をランタイムに伝えます。
no_main
Rust コンパイラに通常のプログラムのエントリーポイントを使わないことを伝えるために、#![no_main] ディレクティブを使います。その代わりに、自分たちでエントリーポイントと main 関数を用意します。
![no_main] を追加すると、デフォルトのプログラムのスタートアップロジックが無効になり、組み込みランタイム(cortex-m-rt など)が制御を引き継げるようになります。
コードを変更する
これらの属性を含めるように、プログラムを更新しましょう。コードエディタでプロジェクトを開き、src/main.rs ファイルを次のように変更してください。
#![no_std] #![no_main] use cortex_m_rt::entry; #[entry] fn main() { println!("Hello, world!"); }
コードを更新すると、rust-analyzer から次のようなエラーが表示されるでしょう。
#![allow(unused)] fn main() { error: `#[entry]` function must have signature `[unsafe] fn() -> !` }
これは、#[entry] 関数が決して return してはいけないためです。プログラムが無期限に実行され、終了しないことを示すため、戻り値の型は !(「never 型」と呼ばれます)でなければなりません。この要件は、組み込みシステムやベアメタルシステムでは非常に重要です。従来のオペレーティングシステム上で動作するアプリケーションとは異なり、プログラムが終了したあとに制御を返す先の OS が存在しないためです。
これを修正するには、main 関数のシグネチャを次のように更新してください。
#[entry] fn main() -> ! { loop { // ずっと実行し続ける } }
Clippy を有効にしている場合は、「empty loop {} wastes CPU cycles.」という警告が表示されるかもしれません。今のところは、この警告は安全に無視できます。
参考資料
-
cortex_m_rt クレートのドキュメント: https://docs.rs/cortex-m-rt/latest/cortex_m_rt/index.html
-
A Freestanding Rust Binary - Start attribute: https://os.phil-opp.com/freestanding-rust-binary/#the-start-attribute