Rust の Mutex / Cell / RefCell / Option 周りの変換メソッド対応表とか

まえがき

最近は自作 OS 開発勉強から派生?寄り道?して、 Rust による組み込み開発を勉強しています。 組み込みの OS とか RP2040 上で動作する自作キーボードのファームウェアなどを書けたらいいなと思っています。

🤤 < べあめたるぷろぐらみんぐ楽しいー !!

ベアメタルプログラミングで頻出する unsafe

組み込み開発では、メモリを管理する OS が存在しないのでヒープ領域にメモリを確保することが難しく、必然的に staticstatic 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 構文は排除していきたいところ。

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 TRef<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();

参考文献


  1. 技術的な話ではなく私の理解が追いついてない部分が大きいですね。 heapless とか linked-list-allocator とか alloc-cortex-m とかを使えばいろいろできる気はします。
  2. ここでいう Mutex<T> は std モジュール内のものではなく、組み込み用途としての cortex_m クレート内のを想定しています。
© 2021 czu.jp