Rustのimpl Traitが使えそうで使えない場所

前提の復習

trait objectについて復習している。各書籍にはどう書いてあったんだっけ。

O'ReillyのプログラミングRust

まず、O'ReillyのプログラミングRustだと、11.1.1 トレイトオブジェクトに書いてある。

www.oreilly.co.jp

勝手に今知りたいポイントだけ書くと、 - trait objectはfat pointer(データへのポインタとvtableへのポインタ) - C++とはvtableの持ち方が違う(C++だと構造体側についている) - あるtrait objectが何を指しているのかは、実行時までわからない(仮想メソッドの呼出エラーをチェックするオーバーヘッドが残る)

なんとなくtrait objectを使うと遅い、とは思っていたが、実は今より書き方が少し古いので、dynとかimplとかは出てこない。

実践Rust入門

gihyo.jp

こちらの書籍では、「8-3 静的ディスパッチと動的ディスパッチ」で書かれているので確認してみる。 要するに、trait objectは動的で、ジェネリクスだと静的ということかな。

簡潔なQ

qnighy.hatenablog.com

でも結局、ここを見るのが一番早い(でも何回も見ている)。特にまとめ。

impl Traitが使えそうだけど…

本題。今回だと、parsingを行う関数をつくりたい場合、Readを実装さえしていれば、.bytes()とかで処理をかけるので、具体的な型を関数定義で書く必要はない。シグニチャはこんな感じ。

fn read_version(reader: &mut impl Read)

実装途中で処理の共通化をしようと思って、追加の引数に関数を渡せるようにした。 このfmap()に渡してcollect()する用の関数です。

fn read_vec<R>(reader: &mut impl Read, f: fn(reader: &mut impl Read) -> R) -> Vec<R>

ところが、これだとエラーが出る。

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src/section.rs:99:59
   |
99 | fn read_vec<R>(reader: &mut impl Read, f: fn(reader: &mut impl Read) -> R) -> Vec<R> {
   |   

前提で確認したとおり、impl Traitが使えるのは以下の2つの場合。

  • 引数で使われた場合 (RFC 1591)
  • 戻り値で使われた場合 (RFC 1522)

で、今回のエラーが出ているのは「引数として渡す関数内の引数」ということで、使える範囲に該当するのでは、と思って書いたのだが、だめだった。

ただ解決は難しくない。 引数でimpl Traitが使われた場合、以下のように翻訳される。

// before
fn give_42_to_callback(callback: impl Fn(i32))
// after
fn give_42_to_callback<F: Fn(i32)>(callback: F)

というわけなので、最初から翻訳後の状態で(impl Traitを使わずに)書けば問題なくビルドが通るようになる。

// before NG
fn read_vec<R>(reader: &mut impl Read, f: fn(reader: &mut impl Read) -> R) -> Vec<R>
// after OK
fn read_vec<T: Read, R>(reader: &mut T, f: fn(reader: &mut T) -> R) -> Vec<R>

むしろ、こっちの方が少し短くなってスッキリするしいい感じかも(それがエラーの原因ではないだろうけど)。