rustをnostdで使う

nostdとは

rustは他の多くの言語と同様,標準ライブラリ(std)が同包されており,通常はそれを使ってプログラミングをおこないます.しかしながら,stdの多くの機能はOSの機能を利用しているため,OSそのものを開発したいとか,あるいはOSが存在しない環境でプログラミングをしたい場合などではstdは利用できません.

stdを利用しない場合は明示的にその旨を宣言する必要がり,そのために利用するのが#![no_std] attributeです.

nostdの概要は(昔の)bookのno stdlibの章に書いてあります. なお,nostdに関わることはunstableな機能が多いので,ここに書いてあることが今後変更される可能性は十分あります

core library

stdが使えなくなるとかなりの機能が制限されてしまいますが,rustはcoreと呼ばれるrust onlyで書かれたライブラリを提供しており,nostd環境でもcoreを利用することができます*1.ただし,core libraryを使用するにあったって,最低限以下の関数は自前で定義する必要があります.

  • memcpy, memcmp, memset
  • panic_fmt
  • eh_personality

memcpy, memcmp, memset

これらの関数はrlibc crateにrustでの実装があるので,必要ならばそれを利用できます.

panic_fmt, eh_personality

プログラムがパニックしたとき等に利用される関数です.とりあえず,何もしないなら

#![feature(lang_items)]

#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] extern fn panic_fmt() -> ! { loop {} }

みたいな関数を作ればokです.

coreとstdの関係

coreのドキュメントを見ていると,stdで提供されている機能のサブセットが提供されていることに気づくと思います.それもそのはずで,これはstdの中でcoreのライブラリがreexportされているからです.coreのドキュメントを見ているとよくstdの方を参照せよと書いてあるのはこういう理由からです. stdはOSがある用,coreはベアメタル用のライブラリですが,stdでもcoreの機能は使いたいのでこうしてるようです.

target

#![no_std]を指定すればnostdなプログラムが開発できるかというと,実際にはそう単純ではありません.多くの場合,プログラムをビルドするためのtargetを正しく設定する必要があります.

rustはさまざまなプラットフォームでの動作を保証しており,それを設定するのがtarget*2です.rustがサポートしているターゲットは ructc --print target-list としてみるか,あるいは https://forge.rust-lang.org/platform-support.html から確認できます.ターゲット名は基本的に{arch}-{vendor}-{sys}[-{abi}]の形をしていて,例えばx86_64-unknown-linux-gnux86_64-apple-darwinのようなターゲットがデフォルトで存在します.

各targetごとに,struct Target及びstruct TargetOptionsで指定されるtargetごとの設定を記述します. rustがデフォルトでサポートしているtargetの設定はsrc/librustc_back/target/の中に書いてあります.例えば,linuxの場合はx86_64_unknown_linux_gnu.rsです.

このtargetはjsonで記述できるようになっており,例えば以下のように記述します.

{
  "llvm-target": "x86_64-unknown-none",
  "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
  "linker-flavor": "gcc",
  "target-endian": "little",
  "target-pointer-width": "64",
  "arch": "x86_64",
  "os": "none",
  "disable-redzone": true,
  "features": "-mmx,-sse,+soft-float",
}

ベースはx86_64_unknown_linux_gnuですが,ここではosが存在しないので"none"を指定し,その他redzonemmx, sseを禁止しています.またfloatのsoftwareサポートを有効にしています.なお,data layoutはLLVMのものです.

こうして作成したtargetのjsonファイルは,例えばx86_64_xxx.jsonという名前で保存したならビルドする際に(Cargo.tomlと同じディレクトリに置いて) cargo build --target=x86_64_xxxのようにすることで指定できます.

sysrootとxargo

さて,targetを無事に指定すればあとはokかというと,残念ながらそうではありません.rustでは実際にプログラムをコンパイルする際に,sysrootと呼ばれるパスからライブラリを検索します.デフォルトのsysrootはrustc --print sysrootで確認できます.このsysrootにcoreのライブラリなどが置かれています.cargo build --target=x86_64_xxx のように指定した場合は x86_64_xxx用のsysrootを探す訳ですが,x86_64_xxxは自分で用意したターゲットなので,sysrootも自分で用意する必要があります.つまり,自分でそのtarget用のcoreをビルドする必要がある訳です.

幸いなことに,自分で用意したtarget用に自動でsysrootを用意してくれる,xargoと呼ばれるプログラムが存在します.cargo install xargoとすればインストールできます.xargoはcargoのラッパーになっており, xargo build --target=x86_64_xxx としたときにtarget x86_64_xxxのsysrootがなければまずそれ用にcoreなどをビルドし,その後プログラムのビルドをおこないます.nostd環境でプログラムを作る際は普通xargoを使うことになると思います.

nostdで使えるcrate

crates.ioのカテゴリ検索で,no-stdを検索すると,stdを利用していないcrateを見つけることができます.といってもCargo.tomlの中でcategoryをちゃんと設定しているcrateがどれだけあるのか分かりませんが..

*1:というか,nostd環境だとstdの代わりにcoreが自動でリンクされるようになる

*2:ちょっと古いのでソースを直接見た方がいいかもしれない