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-gnu
やx86_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"を指定し,その他redzoneやmmx, 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がどれだけあるのか分かりませんが..