panic マクロの一貫性
概要
panic!(..)
では常にformat_args!(..)
が使われるようになりました。つまり、println!()
と同じ書き方をすることになります。panic!("{")
と書くことはできなくなりました。{
を{{
とエスケープしなくてはなりません。x
が文字列リテラルでないときに、panic!(x)
と書くことはできなくなりました。- 文字列でないペイロード付きでパニックしたい場合、
std::panic::panic_any(x)
を使うようにしてください。 - もしくは、
x
のDisplay
実装を用いて、panic!("{}", x)
と書いてください。
- 文字列でないペイロード付きでパニックしたい場合、
assert!(expr, ..)
に関しても同様です。
詳細
panic!()
は、Rust で最もよく知られたマクロの一つです。
しかし、このマクロにはいくぶん非直感的な挙動がありましたが、
今までは後方互換性の問題から修正できませんでした。
// Rust 2018
panic!("{}", 1); // Ok, panics with the message "1"
// OK。 "1" というメッセージと共にパニックする
panic!("{}"); // Ok, panics with the message "{}"
// OK。 "{}" というメッセージと共にパニックする
panic!()
マクロは、2つ以上の引数が渡されたときだけ、フォーマット文字列を使用します。
引数が1つのときは、引数の中身に見向きもしません。
// Rust 2018
let a = "{";
println!(a); // Error: First argument must be a format string literal
// エラー: 第一引数は文字列リテラルでなくてはならない
panic!(a); // Ok: The panic macro doesn't care
// OK: panicマクロは気にしない
その上、このマクロは panic!(123)
のように文字列でない引数を渡すこともできました。
このような使い方は稀で、ほとんど役に立ちません。
というのも、この呼び出しで出力されるメッセージはあきれるほど役に立たないからです: panicked at 'Box<Any>'
(訳: 'Box<Any>
でパニックしました)。
これで特に困るのは、暗黙のフォーマット引数が安定化されたときです。
この機能により、println!("hello {}", name)
の代わりに println!("hello {name}")
と略記できるようになります。
しかし、panic!("hello {name}")
は期待される挙動を示しません。
なぜなら、panic!()
は引数が1つだけ与えられたときにそれをフォーマット文字列として扱わないからです。
この紛らわしい状況を解決するために、Rust 2021 の panic!()
マクロはより一貫したものになりました。
新しい panic!()
マクロは、単一引数として任意の式を受け付けることがなくなりました。
代わりに、println!()
と同様に、常に最初の引数をフォーマット文字列として処理するようになりました。
panic!()
マクロが任意のペイロードを受け付けるわけではなくなったので、
フォーマット文字列以外のペイロードと共にパニックさせる唯一の方法は、
panic_any()
を使うことだけになりました。
// Rust 2021
panic!("{}", 1); // Ok, panics with the message "1"
// Ok。"1" というメッセージと共にパニックする
panic!("{}"); // Error, missing argument
// エラー。引数が足りない
panic!(a); // Error, must be a string literal
// エラー。文字列リテラルでないといけない
さらに、core::panic!()
と std::panic!()
は Rust 2021 で同一のものになります。
現在は、これらの間には歴史的な違いがあり、
#![no_std]
のオンオフを切り替えることで見て取ることができます。
移行
panic
への呼び出しのうち、非推奨の挙動を使用していて Rust 2021 ではエラーになる場所に対して、non_fmt_panics
というリントが発生します。
Rust 1.50 以降、non_fmt_panics
リントはすでにデフォルトで警告として発出されています(後のバージョンではさらにいくつかの機能追加が行われました)。
警告が今現在出ていないコードは、今すぐにでも Rust 2021 に進むことができます!
コードを自動的に Rust 2021 エディションに適合するよう自動移行するか、既に適合するものであることを確認するためには、以下のように実行すればよいです:
cargo fix --edition
手動で移行することを選んだり、そうする必要がある場合には、各 panic の呼び出しについて、 println
と同様のフォーマットに書き換えるか、std::panic::panic_any
を用いて非文字列型のデータと共にパニックさせるかを選ぶ必要があります。
例えば、panic!(MyStruct)
の場合、std::panic::panic_any
(これはマクロでなく関数であることに注意)を使うよう書き換えて、std::panic::panic_any(MyStruct)
とすればよいです。
パニックのメッセージに波括弧が含まれているのに、引数の個数が一致しない場合は(例: panic!("Some curlies: {}")
)、
println!
と同じ構文を使うか(つまり panic!("{}", "Some curlies: {}")
とするか)、波括弧をエスケープすれば(つまり panic!("Some curlies: {{}}")
とすれば)、
その文字列リテラルを用いてパニックすることができます。