bind_interrupts マクロを展開する
先ほど使った bind_interrupts! マクロに戻りましょう。
#![allow(unused)] fn main() { bind_interrupts!(struct Irqs { TEMP => temp::InterruptHandler; }); }
一見すると、この構文は少し奇妙に見えるかもしれません。特に struct の部分です。まるで struct をインスタンス化しようとしているように見えますが、そうではありません。これは単なるマクロへの入力です。マクロは内部で Irqs という名前のユニット struct を作成し、それに必要な割り込みバインディング用の trait を実装します。
ユニット struct はフィールドを持たない空の struct で、最も一般的にはマーカーとして使われます。また、そのサイズは 0 バイトです。
気になる場合は、cargo expand を使って生成されるコードを確認できます。おおよそ次のようになります。
#![allow(unused)] fn main() { use embassy_nrf::interrupt::typelevel; struct Irqs; // これは無視します: ... impl Copy and Clone for Irqs #[allow(non_snake_case)] #[no_mangle] unsafe extern "C" fn TEMP() { <temp::InterruptHandler as typelevel::Handler<typelevel::TEMP,>>::on_interrupt(); } unsafe impl typelevel::Binding<typelevel::TEMP, temp::InterruptHandler,> for Irqs { } }
ここでは、Irqs という名前のユニット struct が作られています。また、TEMP() という関数も作られます。これは、TEMP 割り込みが発生したときにハードウェアが実際に呼び出す割り込みハンドラです。その関数の中で、temp::InterruptHandler の on_interrupt() を呼び出しています。その後、Irqs struct に対して Binding trait を実装しています。これによって Embassy に、TEMP 割り込みが私たちのハンドラに接続されていることを伝えます。
マーカー
Irqs struct の中には関数を何も定義していないのに、なぜその Irqs struct に対して Binding trait を実装するのか疑問に思うなら、その理由を理解するために Temp::new 関数を見る必要があります。
#![allow(unused)] fn main() { // let mut temp = Temp::new(p.TEMP, Irqs); pub fn new( _peri: impl Peripheral<P = TEMP> + 'd, _irq: impl interrupt::typelevel::Binding<interrupt::typelevel::TEMP, InterruptHandler> + 'd, ) -> Self { into_ref!(_peri); // 温度値を通知する割り込みを有効化する interrupt::TEMP.unpend(); unsafe { interrupt::TEMP.enable() }; Self { _peri } } }
ちょっと待ってください。2 番目の引数 _irq はまったく使われていません。では、これは何のためにあるのでしょうか。
直接使われていなくても、その型は重要です。Embassy はこれによって、TEMP 割り込み用のハンドラを設定済みかどうかを確認しています。_irq の型は、TEMP に対する Binding trait を実装していなければなりません。そうでなければ、コードはコンパイルできません。
たとえば、次のように書いたとします。
#![allow(unused)] fn main() { bind_interrupts!(struct Irqs { TWISPI0 => twis::InterruptHandler<peripherals::TWISPI0>; }); }
マクロは TWISPI0 割り込みに対する Binding trait を、次のように生成します。
#![allow(unused)] fn main() { // マーカーを理解するために Binding trait に注目できるよう、これはコメントアウトしています // #[allow(non_snake_case)] // #[no_mangle] // unsafe extern "C" fn TWISPI0() { // <twis::InterruptHandler< // peripherals::TWISPI0, // > as typelevel::Handler< // typelevel::TWISPI0, // >>::on_interrupt(); // } unsafe impl typelevel::Binding<typelevel::TWISPI0,twis::InterruptHandler<peripherals::TWISPI0>,> for Irqs { } }
しかし、これは Temp::new が期待しているものとは一致しません。Temp::new が期待しているのは、TEMP 割り込みに対する Binding です。したがって、コンパイラはエラーを出します。