ウェブと組み込みシステムにおけるRustの実行のための9つのルール

'ウェブと組み込みシステムでRustを実行するための9つのルール'

no_stdとWASMへのポーティングからの実践的な教訓

Crab running on a microcontroller — Source: https://openai.com/dall-e-2/

PythonのメモリセーフティとC++の速度を求める場合、Rustをお勧めします。さらに、Rustでは100,000以上のソフトウェアライブラリを利用することができます。さらに、Rustは従来のコンピュータだけでなく、ウェブページやロボット内でもコードを実行する可能性を提供しています。

ただし、「ほとんどどこでも」実行することには複雑さが伴います。この記事は、これらの複雑さを軽減したいRustプログラマー向けです(Rustの「ほとんどどこでも実行する」ストーリーに興味がある方にも興味があるかもしれません)。

最初の複雑さ:ウェブページとロボットの組み込みプロセッサは一般的なファイルIOをサポートしていません。プロジェクトが主にファイルの読み書きに関するものであれば、ロボット、他の組み込みプロセッサ、またはウェブページでの実行には適していません。

2番目の複雑さ:ほとんどどこでも実行するためには多くのステップと選択肢が必要です。これらの選択肢をナビゲートするのは時間がかかることがあります。ステップを見落とすと失敗する可能性があります。この記事では、これらの9つのルールを提供することで、この2番目の複雑さを軽減することを目指しています。後で詳しく調べることになるでしょう:

  1. lib.rsまたはmain.rsをno_stdとしてマークします。
  2. 可能であれば、組み込みの「crate alloc」を使用します。
  3. 「no std」の依存関係に切り替えます。
  4. stdとallocの機能を作成し、stdのみの関数をオプションにします。
  5. WASM向けにプロジェクトをビルドします。cargo treeを使用して動作させます。
  6. WASMテストとWASMデモを作成します。
  7. [オプション] 組み込み向けにプロジェクトをビルドします。
  8. [オプション] 単一の組み込みテストと組み込みデモを作成します。
  9. CIテスト、Cargo.tomlメタデータ、および更新されたREADME.mdで完了します。

これらのルールに従うことで、PCからスマートフォンのウェブページ(デモ)やロボットまで、どこでも実行される非常に高速でメモリセーフなコードを作成することができます。コードは非常に小さく、巨大なRust crateライブラリを活用することができます。

これらのルールを説明するために、range-set-blaze crateをウェブページ(WASM)およびマイクロコントローラ(組み込み)で実行するようにポートします(このcrateは「塊のある」整数のセットを操作します。 crateのユーザーがポートを要求しました)。

WASMおよび組み込みへのポートでは、Rustの標準ライブラリ「std」の使用を避ける必要があります。「no std」への変換は、私が予想していたよりも簡単であり、難しいです。簡単な理由は、VecStringをまだ使用できるからです。難しい理由は、主にテストのためです。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-blazelib.rsに「no_std」の行を追加すると、40のコンパイラの問題が発生します。ほとんどはこの形式です:

いくつかは、メインコード(テストコードではなく)で「std::」を「core::」に変更することでこれらのいくつかを修正します。 range-set-blaze の場合、これにより問題の数が40から12に減少します。この修正は、std::cmp::max のような多くのアイテムが core::cmp::max としても利用可能であるため、助けになります。

残念ながら、VecBox のようなアイテムは、メモリを割り当てる必要があるため、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-blazerand に依存しています。また、rand は「std」の機能を持つ getrandom に依存していることも示しています。

getrandom のドキュメントを読み、その「js」の機能がWASMをサポートしていることを知りました。では、私たちは「alloc」の機能でコンパイルする場合にのみ、randgetrandom/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 ガイドに基づく手順です:

  1. cargo install wasm-bindgen-cli を実行します。
  2. 現在の統合テストを、例えば tests/integration_tests.rs から tests/wasm.rs にコピーします。 (Rustでは、統合テストは src ディレクトリの外に存在し、プロジェクトの公開メソッドのみを参照するテストです。)
  3. tests/wasm.rs のトップで、#![cfg(test)] を削除し、#![cfg(target_arch = “wasm32”)] および use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); を追加します。
  4. wasm.rs で、すべての #[test]#[wasm_bindgen_test] に置き換えます。
  5. #![cfg(test)] が存在する場所すべて(通常は tests/integration_tests.rs および src/tests.rs )に、追加の行 #![cfg(not(target_arch = "wasm32"))] を追加します。
  6. Cargo.toml[dev-dependencies] を次のように変更します(ある場合):[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
  7. 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 に追加されていないまたは追加できない依存関係を使用しているからです。各問題を確認し、次のいずれかを行います:

  1. 必要な依存関係を Cargo.toml[target.'cfg(target_arch = "wasm32")'.dev-dependencies] セクションに追加するか、
  2. 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 — プロジェクトはこのクレートに依存していましたが、実際には使用していませんでした。依存関係を削除しました。
  • randgetrandom — プロジェクトはランダムな数字が必要なのはネイティブテストのみなので、依存関係を[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]に移動しました。また、メインとテストコードも更新しました。
  • itertoolsnum-traitsnum-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-blazenum-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

以下は、ファイルを作成し、設定する手順です。

  1. 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.rsmemory.xtests/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 checkcargo buildcargo 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!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

機械学習

もし芸術が私たちの人間性を表現する方法であるなら、人工知能はどこに適合するのでしょうか?

MITのポストドクターであるジヴ・エプスタイン氏(SM '19、PhD '23)は、芸術やその他のメディアを作成するために生成的AIを...

人工知能

『ジュリエット・パウエル&アート・クライナー、The AI Dilemma – インタビューシリーズの著者』

『AIのジレンマ』は、ジュリエット・パウエルとアート・クライナーによって書かれましたジュリエット・パウエルは、著者であ...

人工知能

「Kognitosの創設者兼CEO、ビニー・ギル- インタビューシリーズ」

ビニー・ギルは、複数の役職と企業を横断する多様で幅広い業務経験を持っていますビニーは現在、Kognitosの創設者兼CEOであり...

人工知能

『DeepHowのCEO兼共同創業者、サム・ジェン氏によるインタビューシリーズ』

ディープハウのCEO兼共同創設者であるサム・ジェンは、著名な投資家から支持される急速に進化するスタートアップを率いていま...

人工知能

「コーネリスネットワークスのソフトウェアエンジニアリング担当副社長、ダグ・フラーラー氏 - インタビューシリーズ」

ソフトウェアエンジニアリングの副社長として、DougはCornelis Networksのソフトウェアスタック全体、Omni-Path Architecture...

人工知能

「Ntropyの共同創設者兼CEO、ナレ・ヴァルダニアンについて - インタビューシリーズ」

「Ntropyの共同創設者兼CEOであるナレ・ヴァルダニアンは、超人的な精度で100ミリ秒以下で金融取引を解析することを可能にす...