AtCoder で Option も Result も ? で .unwrap() する
競技プログラミングでの Rust のつらさのひとつに .unwrap()
と9文字も書くのがだるいというのがあります。エラー伝搬は ?
1文字なのでエラー無視も楽に書きたいところですが、この記事にあるように stable Rust が入っている競プロサイトでは無理そうに見えます。ところが、実はできます。1
fn main() { main1().ok().unwrap() } fn main1() -> Result<(), Never> { // ここにメインの実装 dbg!("foo 42 bar".split_ascii_whitespace().nth(1)?.parse::<i32>()?); Ok(()) } enum Never {} impl<E: std::fmt::Debug> From<E> for Never { fn from(e: E) -> Self { panic!("unwrap ?: {:?}", e) } }
解説
現在の AtCoder 上の Rust はコンパイラのバージョンが 1.42.0 なので ?
の仕様は (v2 になる前の) try_trait
です。ここで Option
に ?
して Result
を early-return するための型 std::option::NoneError
は #[feature(try_trait)]
がないと名指しできないのですが、 Debug
トレイトを持つすべての型からの変換なら stable の範囲で書けるため、 From<NoneError>
が実装できてしまいます。標準的に使われるエラーは std::error::Error
トレイトなので Debug + Display
以上と分かっているし fn binary_search(&self, x: &T) -> Result<usize, usize>
2 のように「エラー」ではない Result
もだいたい Debug
です。
ちなみに try_trait_v2
では専用の FromResidual
トレイトを実装する必要がある3のでこういう抜け穴はありません。
main 関数に書きたい場合
fn main() -> Result<(), Never> { // ここにメインの実装 Ok(()) } #[derive(Debug)] enum Never {} impl<E: std::fmt::Debug + Clone> From<E> for Never { fn from(e: E) -> Self { panic!("unwrap ?: {:?}", e) } }
Never: Debug
にするとそのまま main
にできるのですが、 T: From<T>
との重複実装が許されないため、変換できるエラー型を絞る必要があり、たとえば Debug + Clone
を課すと良さそうです。 std::marker::PhantomPinned
を用いて Never: Debug + !Unpin
にして E: Debug + Unpin
にできれば使い勝手良くなりそうですが、なぜかできませんでした (T: From<T>
との重複判定が消えないため)。
-
.unwrap()
を直接書く場合と比べるとエラー時に表示されるソースコード位置が変わってしまう問題はあります。RUST_BACKTRACE=1
で1つ下を見ましょう。↩ -
partition_point
が 1.52.0 からなので AtCoder では仕方なく使ったりします。↩ -
try_trait_v2
については以前の日記にも書きました。 https://toslunar.hatenablog.com/entry/2021/08/03/230856↩