最近は自作 OS 開発勉強から派生?寄り道?して、 Rust による組み込み開発を勉強しています。 組み込みの OS とか RP2040 上で動作する自作キーボードのファームウェアなどを書けたらいいなと思っています。
🤤 < べあめたるぷろぐらみんぐ楽しいー !!
組み込み開発では、メモリを管理する OS が存在しないのでヒープ領域にメモリを確保することが難しく、必然的に static
や static mut
な静的変数を宣言してそれを使い回すことが多くなると思います1。
しかしながら、可変で静的な変数の書き換えやアクセスは Rust では unsafe
になってしまいます。
/* 単純な可変な静的変数を扱う場合 */
// 可変な静的変数を用意
static mut UART: Option<Uart> = None;
fn initialize () {
let serial: UART = setup_serial();
// unsafe ブロックで静的変数に代入する必要がある
unsafe { UART = Some(serial); };
}
fn send_hello () {
// unsafe ブロックで取得する必要がある
unsafe { writeln!(UART.as_mut().unwrap(), "hello") };
}
自分で書く小さなプログラムならば、おおよそ動作も把握できているので静的変数と unsafe を使ったほうがコードを早く簡潔に書くことができるのですが、可能であれば(自身の勉強も兼ねて)できるだけ unsafe 構文は排除していきたいところ。
組み込み用のプログラムの場合には、ソースコード上の安全性 (safe/unsafe) に加えて、割り込み処理などを考慮した上で静的変数にアクセスしないといけないため Mutex<T>
や RefCell<T>
、Option<T>
などを使うシーンが頻出しますが、この型の取り回しが初学者には絶望的につらい…2。
/* unsafeを排除して静的変数を扱う場合 */
// Mutex と RefCellでラップした静的変数
static UART: Mutex<RefCell<Option<Uart>>> = Mutex::new(RefCell::new(None));
fn initialize () {
let serial: UART = setup_serial();
// 割り込みを禁止(Mutex)&内部可変性(RefCell)を利用して値を代入
cortex_m::interrupt::free(|cs| UART.borrow(cs).replace(Some(serial)));
}
fn send_hello () {
// 割り込みを禁止(Mutex)&内部可変性(RefCell)を利用して値を取得
cortex_m::interrupt::free(|cs| {
if let Some(ref mut serial) = UART.borrow(cs).borrow_mut().deref_mut() {
writeln!(serial.as_mut(), "hello").unwrap();
}
});
}
この記事では、備忘録として調べたことを書き残しておきたいと思います。
各型の役割や詳細説明は他の解説記事にゆずるとして、各型からの変換メソッドを一覧表形式でまとめてみました。
なお、表にまとめたのは大まかなところのみなので類似のメソッドも含めるともっとたくさんあるのですが、大雑把な理解としてはこれていいと思います。
-> Convert to -> | T (move) | T (copy) | &T | &mut T | *mut T | Ref<T> | RefMut<T> | Option<&T> | Option<&mut T> | Option<&<T as Deref>::Target> | Option<&mut <T as Deref>::Target> |
---|---|---|---|---|---|---|---|---|---|---|---|
Mutex<T> | .borrow(&self, cs) | ||||||||||
Cell<T> | .into_inner(self) | .get(&self) | .get_mut(&mut self) | .as_ptr(&self) | |||||||
RefCell<T> | .into_inner(self) | .get_mut(&mut self) | .as_ptr(&self) | .borrow(&self) | .borrow_mut(&self) | ||||||
Ref<T> | .deref(&self) | ||||||||||
RefMut<T> | .deref(&self) | .deref_mut(&mut self) | |||||||||
Option<T> | .unwrap() | .as_ref(&self) | .as_mut(&mut self) | .as_deref(&self) | .as_deref_mut(&mut self) |
ちなみに、unsafe を使わない方のプログラム後半にある 「静的変数の Mutex<RefCell<Option<Uart>>>
を &mut Uart
にいかに導いていくか」 の部分を順を追って書き下してみるとこんな感じになるハズ。多分。
// 型変換の逐次イメージ
let serial = UART // -> Mutex<RefCell<Option<Uart>>>
.borrow(cs) // -> &RefCell<Option<Uart>>
.borrow_mut() // -> RefMut<Option<Uart>>
.deref_mut() // -> &mut Option<Uart>
.as_mut() // -> Option<&mut Uart>
.unwrap(); // -> &mut Uart (※None なら panic するコードですが、解説のためにあえてこう書いてます)
writeln!(serial, "hello world").unwrap();
// ↑わかりやすいけど、実際は "temporary value dropped while borrowed" のエラーが出てダメ。
// ↓こうすれば動くみたい(変換途中で一時変数のライフタイムが切れてしまうのだと思う)。
let mut serial = UART
.borrow(cs)
.borrow_mut();
let serial = serial
.deref_mut()
.as_mut()
.unwrap();
writeln!(serial, "hello world").unwrap();
Mutex<T>
は std モジュール内のものではなく、組み込み用途としての cortex_m クレート内のを想定しています。↩