Rust信者を増やしたい ~ 組み込み編
はじめに
この記事では、組み込みにおいて Rust がどの程度親和性のあるものなのかについて、僕のとても偏った思想を織り交ぜながら Rust 思想の布教をしていきたいと思います。
余談ですが最近、函館高専のロボコン部も何かで Rust を書いていましたね。それだけ Rust が普及してきたというのは嬉しいことです。
Rust の言語仕様から見る組み込みとの親和性
Rust の特徴はなんといっても変数の管理にあると思います。
メモリの効率を厳格に管理し普通の PC の環境と比較してより意識しなくてはいけない組み込みの環境において、Rust の変数管理はとてもマッチし軽量なシステムを実現します。
Rust の変数
Rust には所有権という概念があります。これは、変数を自由に操作できる最高権限のようなものであり、これがユーザーによって破棄された変数がメモリから消去されます。ここで例を示します。
let a = 0;
let b = a; /// 変数aの値が変数bにそのままコピーされる。
println!("{}", a);
let a = String::from("hello world!!");
let b = a; /// aの変数の所有権がbに移動しaは使用できなくなる。
println!("{}", a); /// Error : aの所有権がなく参照出来ないというエラーが出る。
Rust がとにかく省メモリであるのは、これが理由だと思います。
普通の整数などは、メモリ領域にコピーをしても値が一つなので処理にそんなに時間がかかりません。なので、上の書き方をしたら所有権が移るのではなく値がコピーされます。これはコピートレイトとが実装されているためで、このトレイトが実装されている型の変数は、上の書き方をした場合所有権ではなく値を直接コピーして代入します。
一方、String 型は内部が配列(&[u8])であるためメモリ領域のコピーに時間がかかります。そのため所有権を渡し無駄なコピーを防ぐという目的があります。ユーザーの意思によって必要に応じて clone()関数を叩くことによって元の変数の所有権を失わずに複製をすることが出来ます。
ここらで聞こえてきそうですね。「面倒くせぇ…」
明示的であるという幸せ
確かに Rust は覚えることが多く面倒だと感じる人は多いですよね。それは Rust は適度に明示的でありユーザーの責任範囲が Python 等より広いのです。しかし、安全で堅牢でより確実な安定性を求めるのであれば、何の言語を書こうが確保されたメモリやデータの動きについて後述するレベルの理解は当然に必要となります。
Python による隠蔽
python では全てのオブジェクトに id が割り振られるらしいです。python でも数字などのコピーは値自体をコピーします。これを値渡しと言います。必ず覚えましょう。
また、リストのコピーはそのオブジェクトの id を渡します。これを参照渡しと言います。
仕組みは Rust に似てると思いませんか?
問題はここからです。Rust ではコピーした変数は所有権を失いその値へのアクセス権限を失うわけです。しかし python では、値にアクセスできる変数が2つ生まれてしまうわけです。要するに片方の変更でもう片方も変更されたように見えます。これは理解している人が十分な睡眠を取った後書けば問題ではないかもしれません。しかしミスも生まれます。
ただでさえ他人のバグったコードの読解には骨が折れるのにも関わらず、このような言語仕様による隠蔽まで心配するのは実に非生産的だと思います。
python の設計思想に The Zen of Python というものがあります。その 2 項目に下の言葉があります。
Explicit is better than implicit.
暗示するよりも明示する方が良い。
これは Rust に当てはめるべき言葉だと僕は感じます。
周辺ツールから見る Rust の優位性
コンパイラとターゲット
皆さんは Arm のビルド環境を自分で構築したことはありますか?
Mbed Studio をメインとして使っている人は気にしたことすら無いかもしれませんね。手作業で環境構築をする場合でもこのエディタでも Arm のアーキテクチャ用にビルドするツールチェーンをインストールする必要があります。
(例:Mbed CLI2) https://os.mbed.com/docs/mbed-os/v6.16/build-tools/install-or-upgrade.html
普通の PC で動作するソフトウェアを書く人であれば、組み込みのコードを書く場合いちいちビルドの設定をしてコンパイラも分けてコーディング・デバッグするのはとても面倒ですよね。
C++では、アーキテクチャが変わればコンパイラが変わります。
Platform Supports of Rust
Rust では、設定を書き換えるだけでビルドしたいアーキテクチャ(ターゲット)を変更することが出来ます。
同じビルドセットを利用することができ、新しいターゲットを追加したいときだけ下のようにコマンドを叩くだけです。
ターゲット名は各自調べましょう。
# 例 Cortex-M3 (ARMv7-M architecture):
rustup target add thumbv7m-none-eabi
サポートターゲットリスト
Package Manager - Cargo
Rust には、多分どの言語よりも優秀なパッケージマネージャーがあります。
Rust の設定はすべて、Cargo.toml に記入します。追記しておくだけでビルドのときいい感じにやってくれます。
npm と同じファイルベースのパッケージマネージャでありながら、ビルドターゲット・ビルド時の実行コマンド・最適化度などの設定が出来ます。全能感…
Language Server for Comfortable Coding - rust-analyzer
普段 Vim を使ったりする人であれば LS(ランゲージサーバー:言語のシンタックスハイライト・補完・補助)をインストールしたりする作業をしたことがあるかもしれませんね。言語に LS が存在すればどんなエディタでも補完機能・ハイライト機能を実装出来ます。
特に Rust の LS は優秀です。Rust のコードを高速に読み取り、Cargo.toml の膨大な設定をもとに補完・エラーを表示してくれます。vscode であれば拡張機能から手軽にインストールすることができるため環境構築も手軽に行うことが出来ます。
ライブラリ充実度から見る Rust の優位性
C++のライブラリを導入するとき、皆さんはどうしますか?
Ubuntu などのパッケージとして提供されていれば apt などでインストールしますよね。どこぞのプログラマーが作ったやつであればまず Git で clone してきて、依存関係をインストールして、、、色々面倒ですよね。
Rust では、パッケージのことを crate と呼びます。Rust コミュニティーの提供する豊富なライブラリは下のサイトで検索することが出来ます。
実は Rust は Mozilla によって開発され 2010 年ごろに登場した言語で、意外と長くコアなファンによって支持されてきました。Mozilla の提供する FireFox は Rust で開発されています。そのため堅牢で高速で安定な動作を実現しています。
実は身の回りにも Rust があったこと知ってましたか?
組み込み関係のライブラリ
Rust には、記事執筆時点で 13 年以上という歴史のなかでコミュニティーにより温められてきたライブラリ群があります。
実は、組み込みのライブラリも豊富に存在します。
ベースとなるライブラリ
- embedded-hal:マイコンの基本機能へアクセスするためのクレート
- embedded-time:マイコンの CPU に搭載されているクロックなどを利用するクレート
- cortex_m_rt:Arm Cortex 系の CPU の基本機能を利用
- cortex_m_semihosting:Arm のデバッグ機能、セミホスティング
- panic_halt:マイコンのパニック、停止
マイコン対応ライブラリ
基本的にマイコンの Arm CPU 周辺に搭載される I2C、UART、USB、GPIO、CLOCK などを利用するために使われる。シリーズによって名前が変わる。
- stm32[ board series ]_hal:STM シリーズの HAL
- rp-pico:Raspberry Pi Pico の HAL
- atmega[ board series ]-hal:ATmega シリーズの HAL
no_std 環境での開発を補助するライブラリ
C++で組み込みを行うときに標準ライブラリを使えなくなるように、Rust でも組み込みは#![no_std] & #![no_main]で行います。そのため、標準で提供されているベクターなどが使えなくなり、少し不便な配列だけとなります。そのような開発を少し便利にしてくれるのが下のライブラリです。
- heapless:限界要素数を初期化時に定義するという制限付きではあるがベクターなどのヒープ系の機能を提供
言語文化から見る Rust の優位性
命名規則の統一
Rust のコードは比較的読みやすいと感じています。その訳には、C++と異なり比較的登場時期が最近でありさまざまな言語で蓄積された「命名規則を統一せねばならない」という思想が、キレイに反映されたというのがあると思います。C++のような長い歴史の中で発酵してしまった訳のわからない命名はほとんど見たことがありません。
新しい言語を習得するときには、その言語の表記文化をしっかりとリサーチし遵守することが大切です。
Rust の命名規則
対象 | 規則 | 例 |
---|---|---|
変数 | スネークケース | user_name |
関数 | スネークケース | get_user() |
クラス | アッパーキャメル | ClassName |
Enum | アッパーキャメル | EnumName |
定数 | アッパースネーク | CONST_VALUE |
情報の量と質
Python のような初心者の流入が多い言語コミュニティーでは、しっかりと訓練を積んだ上級者から始めたての初学者までいろいろな人が色々な情報を共有しています。そのため新しい記事と古い記事が乱立しています。
Rust ではそのようなブログ記事こそ少ないですが、パッケージのリポジトリにexamples
をのっける文化があります。
公式が提供している、サンプル記事という安心感もあるためなれている人にとっては良い文化だと思います。
まとめ
Rust は導入が難しいため多くのプログラマーから難しいと言われがちではあります。
しかし、異なる言語を書いていたとしてもいずれは理解しなくていはいけないことを Rust は明示的にしているだけだと僕は思います。なにかの第二言語として学習するのにはうってつけだと思いますのでぜひやっていただきたいです。
(※できればしっかり型のついた言語を第一言語とするのがおすすめです。)
またこの記事は組み込みにフォーカスしています。ぜひ組み込み分野にも Rust のウェーブが広がってほしいと思います。
僕の偏った思想の記事を最後まで読んでいただきありがとうございました。
Discussion