パニック!
エラーには、回復可能なエラーと回復不能なエラーの 2 種類があります。回復不能なエラーとは、システムをそれ以上動作できない状態にしてしまうエラーです。今日は、このエラーに焦点を当てます。
発生するエラーをシミュレートするために、コードにエラーを入れます(後で元に戻します)。
✅ src/scd30/mod.rs に移動します。DEFAULT_ADDRESS を 0x61 から 0x62 に変更します。
#![allow(unused)]
fn main() {
pub const DEFAULT_ADDRESS: u8 = 0x62;
}
この変更により、センサーと開発ボード間の通信ができなくなります。
開発ボードが最初にセンサーと通信しようとするのは、センサーのファームウェア番号を読み取るときです。このメソッドを見てみましょう。
#![allow(unused)]
fn main() {
pub fn get_firmware_version(&mut self) -> Result<[u8; 2], Error> {
let command: [u8; 2] = [0xd1, 0x00];
let mut rd_buffer = [0u8; 2];
self.0.write(DEFAULT_ADDRESS, &command)?;
self.0.read(DEFAULT_ADDRESS, &mut rd_buffer)?;
let major = u8::from_be(rd_buffer[0]);
let minor = u8::from_be(rd_buffer[1]);
Ok([major, minor])
}
}
このメソッドは Result 型を返します。操作の結果に応じて、Result には異なる値が入ります。操作が成功した場合、センサーのファームウェア番号である [u8; 2] 配列が入ります。操作が成功しなかった場合、write() または read() メソッドからのエラーが伝播されて返されます。
プログラムではこのメソッドを呼び出し、unwrap() を追加します。
#![allow(unused)]
fn main() {
let firmware_version_result = sensor.get_firmware_version().unwrap();
}
unwrap() を使用すると、成功時には値を返し、エラー時にはプログラムをパニックさせます。これは、エラーの発生が想定されない場合、またはそのエラーがいずれにせよ回復不能な場合にのみ有用です。今回のケースではこの両方が当てはまるとしても、パニックを手動で処理することにはいくつかの利点があります。そのいくつかを見てみましょう。
✅ プログラムを実行します。
次のような表示になるはずです。
0.000000 ERROR panicked at 'called `Result::unwrap()` on an `Err` value: AddressNack', src/bin/13_scd_30_error_handling.rs:61:28
└─ panic_probe::print_defmt::print @ /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:140
stack backtrace:
0: HardFaultTrampoline
<exception entry>
<exception entry>
1: __udf
2: cortex_m::asm::udf
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.3/src/asm.rs:105
3: rust_begin_unwind
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:75
4: core::panicking::panic_fmt
at /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libcore/panicking.rs:101
5: core::option::expect_none_failed
at /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libcore/option.rs:1272
6: _13_scd_30_error_handling::__cortex_m_rt_main
7: main
at src/bin/13_scd_30_error_handling.rs:19
8: ResetTrampoline
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:547
9: Reset
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:550
The terminal process "/bin/bash '-c', 'cargo run --package knurling-session-20q4 --bin 13_scd_30_error_handling'" terminated with exit code: 134.
Terminal will be reused by tasks, press any key to close it.
最初の行では、unwrap() が呼び出されたときにパニックが発生したこと、およびそれが発生した行が通知されています。メッセージの残りを読んでも、それ以上の情報は分かりませんが、7: で、これが main() の内部で発生したことが分かります。
✅ 次の行を置き換えます。
#![allow(unused)]
fn main() {
let firmware_version = sensor.get_firmware_version().unwrap();
}
次のコードブロックに置き換えます。
#![allow(unused)]
fn main() {
let firmware_version_result = sensor.get_firmware_version();
let firmware_version = match firmware_version_result {
Ok(version_number) => version_number,
Err(error) => {
panic!("Error getting firmware version: {:?}", error)
}
};
}
unwrap() を呼び出す代わりに、match で Result を処理します。エラーの場合、何が起こるかをこちらで決められるようになります。それでもパニックを呼び出すことはできます。
✅ プログラムを実行します。
次のような表示になるはずです。
0.000000 ERROR panicked at 'Error getting firmware version: AddressNack', src/bin/13_scd_30_error_handling.rs:67:13
└─ panic_probe::print_defmt::print @ /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:140
stack backtrace:
0: HardFaultTrampoline
<exception entry>
<exception entry>
1: __udf
2: cortex_m::asm::udf
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.3/src/asm.rs:105
3: rust_begin_unwind
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:75
4: core::panicking::panic_fmt
at /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libcore/panicking.rs:101
5: _13_scd_30_error_handling::__cortex_m_rt_main
at src/bin/13_scd_30_error_handling.rs:67
6: main
at src/bin/13_scd_30_error_handling.rs:19
7: ResetTrampoline
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:547
8: Reset
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:550
The terminal process "/bin/bash '-c', 'cargo run --package knurling-session-20q4 --bin 13_scd_30_error_handling'" terminated with exit code: 134.
Terminal will be reused by tasks, press any key to close it.
エラーメッセージにこちらの部分を追加することで、将来のユーザーはコード行を確認しなくても、プログラムが失敗した理由に加えて、何が失敗したのかも分かるようになります。カスタムエラーメッセージは便利ですが、エラー発生箇所の位置は正確とは言えません。パニックを呼び出した場所はエラーが発生した場所と同じではなく、現在引用されているコード行は panic! が呼び出された行だからです。しかし、これをさらに改善できます!
✅ 上に示したコードブロックを次の行に置き換えます。
#![allow(unused)]
fn main() {
let firmware_version = sensor.get_firmware_version()
.unwrap_or_else(|error| {
panic!("Error getting firmware version: {:?}", error)
});
}
unwrap() を呼び出す代わりに、unwrap_or_else() を呼び出します。unwrap() がエラーの場合にパニックするのに対し、unwrap_or_else() は引数としてクロージャを受け取ることができ、match 文で Result を処理する場合と同じ追加機能を提供できます。
✅ プログラムを実行します。
次のような表示になるはずです。
0.000000 ERROR panicked at 'Error getting firmware version: AddressNack', src/bin/13_scd_30_error_handling.rs:63:5
└─ panic_probe::print_defmt::print @ /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:140
stack backtrace:
0: HardFaultTrampoline
<exception entry>
<exception entry>
1: __udf
2: cortex_m::asm::udf
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.3/src/asm.rs:105
3: rust_begin_unwind
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:75
4: core::panicking::panic_fmt
at /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libcore/panicking.rs:101
5: _13_scd_30_error_handling::__cortex_m_rt_main::{{closure}}
at src/bin/13_scd_30_error_handling.rs:63
6: core::result::Result<T,E>::unwrap_or_else
7: _13_scd_30_error_handling::__cortex_m_rt_main
at src/bin/13_scd_30_error_handling.rs:61
8: main
at src/bin/13_scd_30_error_handling.rs:19
9: ResetTrampoline
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:547
10: Reset
at /Users/tanks/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:550
The terminal process "/bin/bash '-c', 'cargo run --package knurling-session-20q4 --bin 13_scd_30_error_handling'" terminated with exit code: 134.
Terminal will be reused by tasks, press any key to close it.
Result を match で処理する解決策と比べると、少なくともスタックバックトレースでは、単に panic! が呼び出された場所だけでなく、エラーが発生した正確な場所も確認できます。
より詳細なエラーメッセージは便利ですが、私たちはログ出力用のホストマシンなしで実行できるはずの組み込みデバイス上でプログラムしています。独自のパニックハンドラを書くことで、このような場合のための「エラーメッセージ」を提供できます。存在しないホストへログメッセージを送っても役に立たないためです。
✅ scr/rgb_led/mod.rs に移動してください。次のメソッドを追加します。
#![allow(unused)]
fn main() {
pub fn error_blink_red(&mut self, timer: &mut Timer<TIMER0, OneShot>) {
for _i in 0 ..10 {
self.red();
timer.delay_ms(200_u32);
self.off();
timer.delay_ms(200_u32);
}
}
}
呼び出されると、LED は赤色で比較的高速に 10 回点滅します。
✅ panic! を呼び出す直前に、RGB LED でこのメソッドを呼び出してください。
#![allow(unused)]
fn main() {
let firmware_version = sensor.get_firmware_version()
.unwrap_or_else(|error| {
led_indicator.error_blink_red(&mut timer);
panic!("Error getting firmware version: {:?}", error)
});
}
✅ コードを実行してください。
プログラムがパニックする前に、RGB LED が数回赤く点滅するようになります。これにより、何か問題が発生し、デバイスを再起動する必要があること、またはさらなる診断のためにホストへ接続する必要があることをユーザーに知らせます。
✅ src/scd30/mod.rs に移動してください。DEFAULT_ADDRESS を元の値 0x61 に戻します。
#![allow(unused)]
fn main() {
pub const DEFAULT_ADDRESS: u8 = 0x61;
}