ウェブと組み込みシステムにおけるRustの実行のための9つのルール
'ウェブと組み込みシステムでRustを実行するための9つのルール'
no_stdとWASMへのポーティングからの実践的な教訓
PythonのメモリセーフティとC++の速度を求める場合、Rustをお勧めします。さらに、Rustでは100,000以上のソフトウェアライブラリを利用することができます。さらに、Rustは従来のコンピュータだけでなく、ウェブページやロボット内でもコードを実行する可能性を提供しています。
ただし、「ほとんどどこでも」実行することには複雑さが伴います。この記事は、これらの複雑さを軽減したいRustプログラマー向けです(Rustの「ほとんどどこでも実行する」ストーリーに興味がある方にも興味があるかもしれません)。
最初の複雑さ:ウェブページとロボットの組み込みプロセッサは一般的なファイルIOをサポートしていません。プロジェクトが主にファイルの読み書きに関するものであれば、ロボット、他の組み込みプロセッサ、またはウェブページでの実行には適していません。
2番目の複雑さ:ほとんどどこでも実行するためには多くのステップと選択肢が必要です。これらの選択肢をナビゲートするのは時間がかかることがあります。ステップを見落とすと失敗する可能性があります。この記事では、これらの9つのルールを提供することで、この2番目の複雑さを軽減することを目指しています。後で詳しく調べることになるでしょう:
- lib.rsまたはmain.rsをno_stdとしてマークします。
- 可能であれば、組み込みの「crate alloc」を使用します。
- 「no std」の依存関係に切り替えます。
- stdとallocの機能を作成し、stdのみの関数をオプションにします。
- WASM向けにプロジェクトをビルドします。cargo treeを使用して動作させます。
- WASMテストとWASMデモを作成します。
- [オプション] 組み込み向けにプロジェクトをビルドします。
- [オプション] 単一の組み込みテストと組み込みデモを作成します。
- CIテスト、Cargo.tomlメタデータ、および更新されたREADME.mdで完了します。
これらのルールに従うことで、PCからスマートフォンのウェブページ(デモ)やロボットまで、どこでも実行される非常に高速でメモリセーフなコードを作成することができます。コードは非常に小さく、巨大なRust crateライブラリを活用することができます。
これらのルールを説明するために、range-set-blaze
crateをウェブページ(WASM)およびマイクロコントローラ(組み込み)で実行するようにポートします(このcrateは「塊のある」整数のセットを操作します。 crateのユーザーがポートを要求しました)。
WASMおよび組み込みへのポートでは、Rustの標準ライブラリ「std」の使用を避ける必要があります。「no std」への変換は、私が予想していたよりも簡単であり、難しいです。簡単な理由は、Vec
とString
をまだ使用できるからです。難しい理由は、主にテストのためです。range-set-blaze
をベースにした私の経験に基づいて、次のような決定を1つずつ説明します。曖昧さを避けるために、これらの推奨事項をルールとして表現します。
ルール1:lib.rsまたはmain.rsをno_stdとしてマークします。
注:まず、プロジェクトのためにGitを使用して新しいブランチを作成してください。そうすれば、うまくいかない場合でもすべての変更を簡単に元に戻すことができます。
lib.rs
の先頭に次のようにマークします:
#![cfg_attr(not(test), no_std)]
これにより、Rustコンパイラに、テスト以外の場合は標準ライブラリを含まないように指示します。
注1:私のプロジェクトは
lib.rs
を持つライブラリプロジェクトです。main.rs
を持つバイナリプロジェクトの手順はほぼ同じであると考えていますが、テストは行っていません。注2:後のルールでコードのテストについてもっと詳しく話します。
range-set-blaze
のlib.rs
に「no_std」の行を追加すると、40のコンパイラの問題が発生します。ほとんどはこの形式です:
いくつかは、メインコード(テストコードではなく)で「std::」を「core::」に変更することでこれらのいくつかを修正します。 range-set-blaze
の場合、これにより問題の数が40から12に減少します。この修正は、std::cmp::max
のような多くのアイテムが core::cmp::max
としても利用可能であるため、助けになります。
残念ながら、Vec
や Box
のようなアイテムは、メモリを割り当てる必要があるため、core
には含めることができません。しかし、メモリの割り当てをサポートすることに同意するのであれば、それらを使用することができます。
ルール2:可能であれば組み込みの「crate alloc」を使用する。
クレートがメモリの割り当てを許可するべきかどうかはどうですか?WASMの場合は許可するべきです。多くの組み込みアプリケーションでも許可するべきです。ただし、一部の組み込みアプリケーションでは許可すべきではありません。メモリの割り当てを許可することを決定した場合は、lib.rs
の先頭に以下を追加します:
extern crate alloc;
これで、次のような行を追加して多くのメモリ割り当てアイテムにアクセスできます:
extern crate alloc;use alloc::boxed::Box;use alloc::collections::btree_map;use alloc::collections::BTreeMap;use alloc::vec::Vec;use alloc::{format, string::String};use alloc::vec;
range-set-blaze
の場合、これにより問題の数が12から2に減少します。これらの問題はルール3で修正します。
余談:メモリの割り当てができない組み込み環境向けにコードを書いており、
Vec
のような問題が発生していますか?例えば、ベクトルの代わりに配列を使用することができるかもしれません。それでもうまくいかない場合は、他のルールを確認してみてください。何もうまくいかない場合は、クレートをno_std
に移植できないかもしれません。
ルール3:「no std」依存関係に切り替える。
Rustコンパイラは、プロジェクトが「std」関数を使用するクレートを使用した場合に警告を出します。時には、crates.ioで「no_std」クレートの代替品を見つけることができるかもしれません。たとえば、人気のある thiserror
クレートは「std」をコードに挿入します。ただし、コミュニティはそうではない代替品を作成しています。
range-set-blaze
の場合、残りの2つの問題は gen_ops
クレートに関連しています。このクレートは、「+」や「&」などの演算子を便利に定義するための素晴らしいクレートです。ただし、バージョン0.3.0の gen_ops
は「no std」を完全にサポートしていませんでした。しかし、バージョン0.4.0ではサポートしています。私は Cargo.toml
の依存関係を更新し、「no std」互換性を向上させました。
これで、次のコマンドを実行できます:
cargo check # クレートが直接標準ライブラリを使用していないことを確認するcargotest # 標準ライブラリを使用するテストが合格することを確認する
コマンド cargo check
は、私のクレートが直接標準ライブラリを使用していないことを確認します。コマンド cargo test
は、私のテスト(まだ標準ライブラリを使用している)が合格し続けることを確認します。クレートがまだコンパイルできない場合は、次のルールを確認してみてください。
ルール4:stdとallocの機能を作成し、stdのみの関数をオプションにする。
組み込みプロセッサは一般的にファイルの読み書きをサポートしていません。同様に、WASMはまだ完全にファイルをサポートしていません。ファイル関連の「no std」クレートをいくつか見つけることはできますが、包括的なものはありません。したがって、ファイルIOがクレートの中心である場合、WASMや組み込みへの移植は実用的ではありません。
ただし、ファイルIOまたは他のstdのみの関数がクレートにとって重要ではない場合、その関数を「std」の機能としてオプションにすることができます。以下にその方法を示します:
Cargo.toml
に以下のセクションを追加してください:
[package]#...resolver = "2" # Rust 2021+ のデフォルト[features]default = ["std"]std = []alloc = []
これにより、クレートは「std」と「alloc」という2つの機能を持つようになります。デフォルトでは、コンパイラは「std」を使用するはずです。
lib.rs
の先頭に以下のコードを置き換えてください:
#![cfg_attr(not(test), no_std)]
次のように置き換えます:
#![cfg_attr(not(feature = "std"), no_std)]
これにより、「std」機能を適用しない場合、コンパイラは標準ライブラリなしでコンパイルするようになります。
「std」のみのコードの直前の行に #[cfg(feature = "std")]
を配置します。たとえば、ファイルの内容に基づいて RangeSetBlaze
構造体を作成する関数を定義します:
#[cfg(feature = "std")]use std::fs::File;#[cfg(feature = "std")]use std::io::{self, BufRead};#[cfg(feature = "std")]use std::path::Path;#[cfg(feature = "std")]#[allow(missing_docs)]pub fn demo_read_ranges_from_file<P, T>(path: P) -> io::Result<RangeSetBlaze<T>>where P: AsRef<Path>, T: FromStr + Integer,{ //...コードは省略}
「std」と「alloc」の機能をチェックするには、次のようにします:
cargo check # stdcargo check --features alloc --no-default-features
「std」をテストするには、次のコマンドを使用します:
cargo test
余談:意外にも、
cargo test --features alloc --no-default-features
は “alloc” をテストしません。これは、テストにはスレッド、アロケーションなどが必要であり、no_std
では利用できないため、cargoは常に通常のテストを “std” として実行するためです。
この時点で、”std” と “alloc” の両方をチェックしていますが、ライブラリがWASMと組み込みで動作することを前提にすることはできません。一般的には、テストを行わない限り、何も動作しないことになります。具体的には、内部で「std」コードを使用するクレートに依存している可能性があります。これらの問題を見つけるためには、WASMおよび組み込み環境でテストする必要があります。
ルール5:WASM用にプロジェクトをビルドします。cargo treeを使用して動作させます。
WASMのクロスコンパイラをインストールし、次のコマンドでプロジェクトをチェックします:
rustup target add wasm32-unknown-unknown # 一度だけ実行する必要があります# 問題が見つかるかもしれませんcargo check --target wasm32-unknown-unknown --features alloc --no-default-features
range-set-blaze
でこれを実行すると、getrandom
クレートがWASMで動作しないというエラーが表示されます。一方、私のプロジェクトは直接 getrandom
に依存していないため、驚きました。間接的な依存関係を見つけるために、cargo tree
を使用します。次のコマンドで依存関係を調べます:
cargo tree --edges no-dev --format "{p} {f}" --features alloc --no-default-features
このコマンドは、クレートと使用している機能を出力します:
range-set-blaze v0.1.6 (O:\Projects\Science\wasmetc\wasm3) alloc├── gen_ops v0.4.0├── itertools v0.10.5 default,use_alloc,use_std│ └── either v1.8.1 use_std├── num-integer v0.1.45 default,std│ └── num-traits v0.2.15 default,std│ [build-dependencies]│ └── autocfg v1.1.0│ [build-dependencies]│ └── autocfg v1.1.0├── num-traits v0.2.15 default,std (*)├── rand v0.8.5 alloc,default,getrandom,libc,rand_chacha,std,std_rng│ ├── rand_chacha v0.3.1 std│ │ ├── ppv-lite86 v0.2.17 simd,std│ │ └── rand_core v0.6.4 alloc,getrandom,std│ │ └── getrandom v0.2.9 std│ │ └── cfg-if v1.0.0...
出力によると、range-set-blaze
は rand
に依存しています。また、rand
は「std」の機能を持つ getrandom
に依存していることも示しています。
getrandom
のドキュメントを読み、その「js」の機能がWASMをサポートしていることを知りました。では、私たちは「alloc」の機能でコンパイルする場合にのみ、rand
が getrandom/js
を使用するように伝える方法はどうすればいいでしょうか? 以下のように Cargo.toml
を更新します:
[features]default = ["std"]std = ["getrandom/std"]alloc = ["getrandom/js"][dependencies]# ...getrandom = "0.2.10"
これにより、「std」の機能は getrandom
の「std」の機能に依存することがわかります。しかし、「alloc」の機能は、getrandom
の「js」の機能を使用する必要があります。
これが機能します:
cargo check --target wasm32-unknown-unknown --features alloc --no-default-features
したがって、WASMのコンパイルは成功しましたが、WASMのテストはどうなりますか?
ルール6:WASMのテストとWASMデモを作成する。
まずテスト、それからデモのウェブページでWASMバージョンを活用しましょう。
tests/wasm.rs
にWASMテストを作成する
WASM上でテストを行うことは、ネイティブにテストするのとほぼ同じくらい簡単です。これは、元のテストをネイティブで実行する一方で、ほとんど同じテストセットをWASM上で実行することによって行います。以下は、wasm-bindgen
ガイドに基づく手順です:
cargo install wasm-bindgen-cli
を実行します。- 現在の統合テストを、例えば
tests/integration_tests.rs
からtests/wasm.rs
にコピーします。 (Rustでは、統合テストはsrc
ディレクトリの外に存在し、プロジェクトの公開メソッドのみを参照するテストです。) tests/wasm.rs
のトップで、#![cfg(test)]
を削除し、#![cfg(target_arch = “wasm32”)]
およびuse wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser);
を追加します。wasm.rs
で、すべての#[test]
を#[wasm_bindgen_test]
に置き換えます。#![cfg(test)]
が存在する場所すべて(通常はtests/integration_tests.rs
およびsrc/tests.rs
)に、追加の行#![cfg(not(target_arch = "wasm32"))]
を追加します。Cargo.toml
の[dev-dependencies]
を次のように変更します(ある場合):[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
Cargo.toml
に次のセクションを追加します:
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]wasm-bindgen-test = "0.3.37"
これらの設定が完了したら、ネイティブテスト(cargo test
)は引き続き機能するはずです。 Chrome ブラウザがインストールされていない場合は、インストールしてください。次に、次のコマンドでWASMテストを実行してみてください:
wasm-pack test --chrome --headless --features alloc --no-default-features
おそらく失敗するはずです。なぜなら、WASMテストが Cargo.toml
に追加されていないまたは追加できない依存関係を使用しているからです。各問題を確認し、次のいずれかを行います:
- 必要な依存関係を
Cargo.toml
の[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
セクションに追加するか、 tests/wasm.rs
からテストを削除します。
range-set-blaze
については、パッケージのベンチマークフレームワークをテストする関連するすべてのWASMテストを削除しました。これらのテストはネイティブ側で実行されます。 tests\wasm.rs
のいくつかの有用なテストには syntactic-for
クレートが必要でしたので、 Cargo.toml
の [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
の下に追加しました。 これにより、59個のWASMテストが実行され、合格しました。
余談:プロジェクトにexamplesフォルダが含まれている場合、
native
モジュールとwasm
モジュールを作成する必要がある場合があります。これについては、range-set-blaze
ファイルを参考にしてください。
tests/wasm-demo
にWASMデモを作成する
WASMをサポートする楽しみの一部は、Rustコードをウェブページでデモできることです。以下は range-set-blaze
のウェブデモです。
以下の手順に従って独自のウェブデモを作成してください:
プロジェクトのメインの Cargo.toml
ファイルで、ワークスペースを定義し、tests/wasm-demo
を追加します:
[workspace]members = [".", "tests/wasm-demo"]
テストフォルダーに test/wasm-demo
サブフォルダーを作成します。
次のような新しい Cargo.toml
を作成します(range-set-blaze
をプロジェクトの名前に変更してください):
[package]name = "wasm-demo"version = "0.1.0"edition = "2021"[lib]crate-type = ["cdylib"][dependencies]wasm-bindgen = "0.2"range-set-blaze = { path = "../..", features = ["alloc"], default-features = false}
また、tests/wasm-demo/src/lib.rs
ファイルを作成します。以下は私のファイルです:
#![no_std]extern crate alloc;use alloc::{string::ToString, vec::Vec};use range_set_blaze::RangeSetBlaze;use wasm_bindgen::prelude::*;#[wasm_bindgen]pub fn disjoint_intervals(input: Vec<i32>) -> JsValue { let set: RangeSetBlaze<_> = input.into_iter().collect(); let s = set.to_string(); JsValue::from_str(&s)}
このファイルは、disjoint_intervals
という名前の関数を定義しています。この関数は整数のベクターを入力として受け取ります。例えば、100,103,101,102,-3,-4
です。 range-set-blaze
パッケージを使用して、関数は整数をソートされた、重複しない範囲として表す文字列を返します。例えば、-4..=-3, 100..=103
です。
最後のステップとして、tests/wasm-demo/index.html
ファイルを作成します。私のファイルでは、JavaScriptを少し使用して整数のリストを受け入れ、Rust WASM関数 disjoint_intervals
を呼び出します。
<!DOCTYPE html><html><body> <h2>Rust WASM RangeSetBlaze Demo</h2> <p>カンマ区切りの整数リストを入力してください:</p> <input id="inputData" type="text" value="100,103,101,102,-3,-4" oninput="callWasmFunction()"> <br><br> <p id="output"></p> <script type="module"> import init, { disjoint_intervals } from './pkg/wasm_demo.js'; function callWasmFunction() { let inputData = document.getElementById("inputData").value; let data = inputData.split(',').map(x => x.trim() === "" ? NaN : Number(x)).filter(n => !isNaN(n)); const typedArray = Int32Array.from(data); let result = disjoint_intervals(typedArray); document.getElementById("output").innerHTML = result; } window.callWasmFunction = callWasmFunction; init().then(callWasmFunction); </script></body></html>
デモをローカルで実行するには、まずターミナルをtests/wasm-demo
に移動してください。次に、以下のコマンドを実行してください:
# tests/wasm-demoからwasm-pack build --target webを実行する
次に、ローカルのウェブサーバーを起動し、ページを表示します。私はLive Preview拡張機能をVS Codeで使用しています。多くの人がpython -m http.server
を使用しています。 range-set-blaze
デモは次のように表示されます(GitHubでライブで利用可能):
ウェブページでRustプロジェクトが実行されるのを見るのは非常に満足感があります。WASM互換性だけを求めている場合は、ルール9に進んでください。
ルール7: 組み込み向けにプロジェクトをビルドする。
WASM以上のことをしたい場合は、このルールと次のルールに従ってください。
ターミナルをプロジェクトのホームディレクトリに戻してください。次に、以下のコマンドで人気のある組み込みプロセッサであるthumbv7m-none-eabi
をインストールし、プロジェクトをチェックしてください:
# プロジェクトのホームディレクトリからrustup target add thumbv7m-none-eabiを実行する# 一度だけ実行する必要があります# 問題を見つけるかもしれませんcargo check --target thumbv7m-none-eabi --features alloc --no-default-features
私がrange-set-blaze
でこれを実行すると、以下の4つの依存関係に関連するエラーが発生します:
thiserror
— プロジェクトはこのクレートに依存していましたが、実際には使用していませんでした。依存関係を削除しました。rand
とgetrandom
— プロジェクトはランダムな数字が必要なのはネイティブテストのみなので、依存関係を[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
に移動しました。また、メインとテストコードも更新しました。itertools
、num-traits
、num-integer
— これらのクレートは「std」と「alloc」の機能を提供します。私はCargo.toml
を以下のように更新しました:
...[features]default = ["std"]std = ["itertools/use_std", "num-traits/std", "num-integer/std"]alloc = ["itertools/use_alloc", "num-traits", "num-integer"][dependencies]itertools = { version = "0.10.1", optional = true, default-features = false }num-integer = { version = "0.1.44", optional = true, default-features = false }num-traits = { version = "0.2.15", optional = true, default-features = false }gen_ops = "0.4.0"[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]#...rand = "0.8.4"#...
どの依存関係のどの機能を使用するかはどのように知ったのでしょうか? itertools
などのクレートの機能を理解するには、そのドキュメンテーションを読み、(しばしば)GitHubリポジトリにアクセスし、Cargo.toml
を読む必要があります。また、各依存関係から望ましい機能を取得していることを確認するためにcargo tree
を使用するべきです。たとえば、このcargo tree
の使用例では、デフォルトのコンパイルではrange-set-blaze
、num-integer
、およびnum-traits
の「std」機能、itertools
およびeither
の「use-std」機能を取得していることがわかります:
cargo tree --edges no-dev --format "{p} {f}"
range-set-blaze v0.1.6 (O:\Projects\Science\wasmetc\wasm4) default,itertools,num-integer,num-traits,std├── gen_ops v0.4.0├── itertools v0.10.5 use_alloc,use_std│ └── either v1.8.1 use_std├── num-integer v0.1.45 std│ └── num-traits v0.2.15 std│ [build-dependencies]│ └── autocfg v1.1.0│ [build-dependencies]│ └── autocfg v1.1.0└── num-traits v0.2.15 std (*)
そして、--features alloc --no-default-feature
のコンパイルでは、itertools
の望ましい「use_alloc」機能と他の依存関係の「no default」バージョンが得られることが示されています:
cargo tree --edges no-dev --format "{p} {f}" --features alloc --no-default-features
range-set-blaze v0.1.6 (O:\Projects\Science\wasmetc\wasm4) alloc,itertools,num-integer,num-traits├── gen_ops v0.4.0├── itertools v0.10.5 use_alloc│ └── either v1.8.1├── num-integer v0.1.45│ └── num-traits v0.2.15│ [build-dependencies]│ └── autocfg v1.1.0│ [build-dependencies]│ └── autocfg v1.1.0└── num-traits v0.2.15 (*)
すべてがうまく動作していると思ったら、次のコマンドを使用してネイティブ、WASM、および組み込みをチェック/テストしてください:
# ネイティブをテストcargo testcargo test --features alloc --no-default-features# WASMをチェックおよびテストcargo check --target wasm32-unknown-unknown --features alloc --no-default-featureswasm-pack test --chrome --headless --features alloc --no-default-features# 組み込みをチェックcargo check --target thumbv7m-none-eabi --features alloc --no-default-features
これらは組み込みをチェックしますが、組み込みをテストする方法はありますか?
ルール8:単一の組み込みテストと組み込みデモを作成します。
埋め込み機能を使用して、組み込みテストとデモを組み合わせて作成します。これをQEMUというエミュレータで実行します。
ネイティブRustのテストは簡単です。WASM Rustのテストは問題ありません。組み込みRustのテストは難しいです。単一のシンプルなテストのみ行います。
注1: 組み込みRustの実行とエミュレーションについての詳細は、The Embedded Rust Bookを参照してください。
注2: 組み込みRustのより完全なテストフレームワークのアイデアについては、defmt-testを参照してください。残念ながら、私はエミュレーションでの実行方法がわからなかったため、cortex-m/testsuiteプロジェクトはdefmt-testのフォークを使用し、エミュレーションで実行することができますが、スタンドアロンのテスト用のクレートは提供せず、さらに3つの追加の(サブ)プロジェクトが必要です。
注3: 組み込みテストはまったくテストがないよりもはるかに優れています。ネイティブおよびWASMレベルでのテストの残りを行います。
現在のtests
フォルダ内に埋め込みのテストとデモを作成します。ファイルは以下のようになります:
tests/embedded├── .cargo│ └── config.toml├── Cargo.toml├── build.rs├── memory.x└── src └── main.rs
以下は、ファイルを作成し、設定する手順です。
- QEMUエミュレータをインストールします。Windowsでは、インストーラーを実行し、手動で
"C:\Program Files\qemu\"
をパスに追加する必要があります。
2. tests/embedded/Cargo.toml
を作成し、ローカルプロジェクトに「デフォルトの機能なし」と「alloc」を依存関係として持つようにします。以下は私のものです:
[package]edition = "2021"name = "embedded"version = "0.1.0"[dependencies]alloc-cortex-m = "0.4.4"cortex-m = "0.6.0"cortex-m-rt = "0.6.10"cortex-m-semihosting = "0.3.3"panic-halt = "0.2.0"# ローカルプロジェクトを参照してくださいrange-set-blaze = { path = "../..", features = ["alloc"], default-features = false }[[bin]]name = "embedded"test = falsebench = false
3. tests/embedded/src/main.rs
というファイルを作成します。テストコードは「test goes here」コメントの後に入れます。以下は私のファイルです:
// https://github.com/rust-embedded/cortex-m-quickstart/blob/master/examples/allocator.rs// および https://github.com/rust-lang/rust/issues/51540に基づく#![feature(alloc_error_handler)]#![no_main]#![no_std]extern crate alloc;use alloc::string::ToString;use alloc_cortex_m::CortexMHeap;use core::{alloc::Layout, iter::FromIterator};use cortex_m::asm;use cortex_m_rt::entry;use cortex_m_semihosting::{debug, hprintln};use panic_halt as _;use range_set_blaze::RangeSetBlaze;#[global_allocator]static ALLOCATOR: CortexMHeap = CortexMHeap::empty();const HEAP_SIZE: usize = 1024; // バイト単位#[entry]fn main() -> ! { unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) } // test goes here let range_set_blaze = RangeSetBlaze::from_iter([100, 103, 101, 102, -3, -4]); assert!(range_set_blaze.to_string() == "-4..=-3, 100..=103"); hprintln!("{:?}", range_set_blaze.to_string()).unwrap(); // QEMUを終了/注意: これをハードウェア上で実行しないでください。OpenOCDの状態を破損する可能性があります。 debug::exit(debug::EXIT_SUCCESS); loop {}}#[alloc_error_handler]fn alloc_error(_layout: Layout) -> ! { asm::bkpt(); loop {}}
4. cortex-m-quickstart
のGitHubからbuild.rs
とmemory.x
をtests/embedded/
にコピーします。
5. tests/embedded/.cargo/config.toml
を作成し、以下の内容を含めます:
[target.thumbv7m-none-eabi]runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"[build]target = "thumbv7m-none-eabi"
6. プロジェクトのメインCargo.toml
を更新し、tests/embedded
をワークスペースに追加します:
[workspace]members = [".", "tests/wasm-demo", "tests/embedded"]
このセットアップで、埋め込みシステムのエミュレーションを実行する準備がほぼ整いました。次に、ターミナルを準備し、コンパイラをナイトリービルドに設定します:
# qemuがパスにあることを確認してください。例えば、PATH="C:\Program Files\qemu\";%PATH%cd tests/embeddedrustup override set nightly # `#![feature(alloc_error_handler)]`をサポートするため
これで、デモアプリケーションでcargo check
、cargo build
、cargo run
を使用できます。例:
cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.03s Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel O:\Projects\Science\wasmetc\wasm4\target\thumbv7m-none-eabi\debug\embedded`Timer with period zero, disabling"-4..=-3, 100..=103"
これが動作すれば、(エミュレートされた)マイクロコントローラ上でプロジェクトを正常に実行できます!セットアップに問題がある場合は、この手順を再確認してください。それでもうまくいかない場合は、The Embedded Rust Bookを参照してください。
作業が完了したら、コンパイラを安定版に戻すことを忘れないでください:
rustup override set stable
ルール9: CIテスト、Cargo.tomlのメタデータ、および更新されたREADME.mdで完了する。
CIテスト
ほぼ完了ですが、今日動作するものが明日も動作することを確認する必要があります。それがCI(継続的インテグレーション)テストの役割です。
CIテストを実行するため、チェックインごとと毎月1回実行されるように設定します。GitHubの場合、.github/workflows/tests.yml
というファイルを作成します:
name: teston: push: schedule: # 毎月実行 - cron: '0 0 1 * *' pull_request: workflow_dispatch: jobs: test_rust: name: Test Rust runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Rust uses: dtolnay/rust-toolchain@master with: toolchain: stable - name: Setup WASM uses: jetli/[email protected] - name: Test Native & WASM run: | cargo clippy --verbose --all-targets --all-features -- -D warnings cargo test --verbose cargo test --features alloc --no-default-features --verbose wasm-pack test --chrome --headless --features alloc --no-default-features --verbose - name: Setup and check Embedded run: | rustup target add thumbv7m-none-eabi cargo check --target thumbv7m-none-eabi --features alloc --no-default-features rustup override set nightly rustup target add thumbv7m-none-eabi cargo check --target thumbv7m-none-eabi --features alloc --no-default-features sudo apt-get update && sudo apt-get install qemu qemu-system-arm - name: Test Embedded (in nightly) timeout-minutes: 3 run: | cd tests/embedded cargo run
WASMのみを行っている場合は、埋め込みに関連する最後の2つのステップを省略することができます。
傍注:なぜ最後のテストに
timeout-minutes: 3
と表示されているのか?それは、失敗した埋め込みテストが失敗で返らないからです。代わりに、無限ループに入ります。これをタイムアウトで捕捉します。
メタデータ
Rustでは、コードを特定のアーキテクチャと環境で動作するようにマークすることができます。慣例として、キーワードとカテゴリのメタデータを使用します。具体的には、適切な箇所に以下のキーワードとカテゴリをCargo.toml
に追加してください。
[package]#...keywords = [#... "wasm", "no_std",]categories = [#... "wasm", "no-std",]
README.md
README.md
も更新して、WASMと組み込みをサポートしていることを伝えるべきです。以下は、追加した内容です。
The crate supports no_std, WASM, and embedded projects:```toml[dependencies]range-set-blaze = { features = ["alloc"], default-features = false, version=VERSION }``` *VERSIONを現在のバージョンに置き換えてください。
以上がRustでWASMとno_stdのポートを行うための9つのルールです。Rustは、ネイティブ、WASM、組み込みプログラミングにおいて優れた言語です。高速性、安全性、そして数千もの有用なクレートへのアクセスを提供します。これらの9つのルールに従って、自分自身のRustコードをほとんどどこでも実行してください。
傍注: もし将来の記事に興味がある場合は、VoAGIで私をフォローしてください。私はRustとPythonでの科学的なプログラミング、機械学習、統計について書いています。月に1つの記事を書く傾向があります。
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles