🦤
Rustのmatchのネストが深くなりすぎたときの一工夫
最近またRustを勉強し始めています。
気がついたらmatchのネストが深くなりすぎていた
Result
の処理をするのにmatch
が便利です。
でもOk
のときに続きの処理を書いていったら、ネストが深くなりすぎてしまいました。
こんな感じ。
loop {
match reader.read_ivf_frame_header() {
Ok(frame_header) => {
let len: usize = frame_header.frame_size as _;
match reader.read_frame(&mut frame_buffer[..len]) {
Ok(_) => {}
Err(ref e) if e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
}
match vp8dec.decode(&frame_buffer[..len]) {
Ok(raw_video) => match outfile.write_all(raw_video) {
Ok(_) => {}
Err(ref e) if e.kind() == ErrorKind::BrokenPipe => break,
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
},
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
}
}
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
}
frame_index += 1;
}
match が3段にネストしてしまって、インデントの対応も見づらくなっています。
これでは見通しが悪いので、ネストが深くならないように書き直したいですね。
and_then を試す
やってみたけど、今回の場合はダメでした。クロージャの中からはbreak
は使えないということに、コンパイルエラーになってから気がつきました。
unwrap, expect を使う
エラーのときには、そのエラーの種別も見ずにpanicさせるなら、unwrap
やexpect
を使うことですっきりさせることもできますが、今回は見送りました。
let var = some_func().expect("Failed in some_func()");
let var = match ... を使う
ネストが深くなっているのは、Ok(var)
のところに、正常系の続きの処理を埋め込んでしまっているためです。
ネストが深くならないようにするためには、matchが正常のときの値を返すようにし、それをmatchの外側で取り出します。
let var = match some_func() {
Ok(f) => f,
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
};
このように Ok(f) => f
とすれば、正常の値をmatch
の外に持っていけます。
今回の場合はErr
の場合は全て、break
, continue
, return
のような制御構文になっているのでこれでうまくいきました。
書き直した結果
loop {
let frame_header = match reader.read_ivf_frame_header() {
Ok(f) => f,
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
};
let len: usize = frame_header.frame_size as _;
match reader.read_frame(&mut frame_buffer[..len]) {
Ok(_) => {}
Err(ref e) if e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
}
let raw_video = match vp8dec.decode(&frame_buffer[..len]) {
Ok(r) => r,
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
};
match outfile.write_all(raw_video) {
Ok(_) => {}
Err(ref e) if e.kind() == ErrorKind::BrokenPipe => break,
Err(e) => {
eprintln!("Error: {e:?}");
break;
}
}
frame_index += 1;
}
ネストが深くなりすぎずに処理の順を追うのが楽になりました。
let-else
Err
の処理が1種類しかない場合にはlet-else
の構文が使えるかなと思ったのですが、これだとErr
の種別がわからないですね。今回は見送りです。
Discussion
このような状況だと,ループ内部を関数にして
?
演算子を使うという方法も適している気がしますが,いかがでしょうか?もちろんそれもありますね