Skip to content

Instantly share code, notes, and snippets.

@sile
Last active Aug 1, 2022
Embed
What would you like to do?
Rustの『RFC 2113: dynトレイト構文』の要約メモ

RFC 2113: dynトレイト構文

要約

  • トレイトオブジェクトのためにdyn Trait構文を導入
    • "bare trait"構文は非推奨扱い
  • dynキーワード:
    • 最初は文脈的(contextual)
    • 将来的には真のキーワードにする

動機

現在の構文は、しばしば曖昧で紛らわしい

以下の例のように、トレイトとトレイトオブジェクトの見た目が区別できない:

  • impl<T> SomeTrait for T where T: AnotherTraintとすべきところで、Impl SomeTrait for AnotherTraitと書かれていることがある
  • impl MyTrait {}は有効な構文:
    • これは「トレイト自身にに対する操作」といった(間違った)印象を与えやすい
      • e.g., デフォルト実装や拡張メソッドの追加
    • 実際には、トレイトオブジェクトへの固有メソッドの追加、となる
  • 関数ポインタと関数トレイトオブジェクトの型が間違いやすい:
    • 前者は&fn ...で、後者は&Fn ..
    • 一文字しか違わない

通常、これらのミスを犯してもは、不親切なコンパイルエラーメッセージしか表示されない:

  • e.g., 「このトレイトはSizedを未実装」
  • 今ならもっと良いメッセージを出せそうではあるけど、そもそも"明確に悪い"構文が技術的に合法となってしまっているので、コンパイラが出来ることには限界がある

代替手段よりも使用頻度が少ない(のにトレイトオブジェクトの方が簡潔に記述可能)

トレイトオブジェクトでも実現可能だけど、代替案の方が適切な例:

  • コンテナ等に複数種類の値を格納したい場合:
    • enumの方が適切
  • あるトレイトを実装した型を、その名前を記述せずに返したい場合:
    • implトレイトの方が適切(安定化されたら)
  • あるトレイトを実装した型の値を引数で受け取りたい場合:
    • ジェネリクスの方が適切

トレイトオブジェクトが最適な解法であるケースも多くあるけど、上に挙げた例ほど一般的ではない。

時々、代替手段よりも遅い(のにトレイトオブジェクトの方が...)

  • トレイトオブジェクトには動的ディスパッチのコストがある
  • enumやimplトレイトには、そのようなコストはない

しばしば、代替手段では可能なことが実現できない(のにトレイトオブジェクトの方が...)

多くのトレイトは、トレイトオブジェクト化を行えない:

  • オブジェクトの安全性ルールを満たしていないため
    • e.g., ジェネリクスを使うメソッドを持っている
  • implトレイトやジェネリクスは、任意のトレイトと併用が可能

implトレイトは、イディオムの大幅なシフトや独自の教材を必要とする

implトレイトの導入で望ましいイディオムが変わる:

  • 現状だと、名前無しであるトレイトの実装を返そうとすると、通常Boxでトレイトオブジェクトを包むことになる
  • クロージャ、イテレータ、フューチャー、結合子、等を返す関数の大半がこれに該当する
  • => これらは(安定化されたら)implトレイトに切り替えるべき

トレイトシステムをを教える方法にも変更が必要:

  • implトレイトを説明する際には、ジェネリクスやトレイトオブジェクト等のトレイトの既存の利用法と合わせて行う必要がある
    • それら(やenumのような類似機能)ではなくimplトレイトを使う方が適切なケースについても書く
  • クロージャやイテレータについて教える際には、implトレイトがなぜ有用か、を多くの例と共に伝える
  • 同様にimplトレイトでは不十分で、dynトレイトが必要なケースにも言及

つまり(理想的には):

  • implトレイトを導入するなら、いずれにせよいろいろと変更は必要
  • dynトレイトをさらに追加しても、あまり余計なコストは掛からない

"dynトレイト vs implトレイト"の構図は、教育および人間工学的に"bareトレイト vs implトレイト"よりも好ましい

専用キーワード:

  • impl/dynキーワードは、static/dynamicディスパッチの関係を素直に反映している
  • 両方のディスパッチ方法に、専用のキーワードがあることで、その差異や選択の重要性が適切に示唆される
  • 現状の構文だと「トレイトオブジェクトがデフォルト」でimplトレイトはよりニッチな機能だという印象を与えかねない

誤用時のケア:

  • implトレイトが安定化したら、間違えてトレイトオブジェクトを書いてしまうことは普通にありそう
    • e.g., implキーワードの記述漏れ
  • こういったケースでは、不親切で難解なエラーメッセージが出力されがち
    • e.g., "your trait not implementing Sized"
  • dynトレイトに切り替われば、エラーはもっと単純かつ自明になる
    • e.g., "expected a type, found a trait, did you mean to write impl rait?"

説明

dyn Traitは機能的には、現状のトレイトオブジェクト構文と同様:

  • Box<Trait>Box<dyn Trait>になる
  • &Traitおよび&mut Traitは、それぞれ&dyn Traitおよび&mut dyn Traitになる

移行

現在のエポック:

  • 文脈的なdynキーワードを追加
  • bareトレイト構文用のlintを追加

次のエポック:

  • dynが真のキーワードになる
    • 識別子として使われているとハードエラーとなる
  • bareトレイト構文用のlintが「デフォルトで拒否」に変更される

これはエポックRFCのポリシーに従っている:

  • ハードエラーは「コードの小さな割合のみが影響を受ける」場合のみ可能
  • dynキーワードの追加の影響はおそらく小さい
  • bareトレイト構文の除去は、影響が大きいのでlintでのみ対応

欠点

  • 新しいキーワードの導入
  • トレイトオブジェクトの使用コードが若干冗長になる
  • &dyn Traitは、&dynが(&&mutに加えて)参照の第三番目の種類である、といった印象を与えるかもしれない
  • 一般的に、(この変更が勧める)ジェネリクスは、トレイトオブジェクトよりもコンパイルに時間が掛かる

理論的根拠と代替案

別のキーワードが適切だったかも:

  • 例えばobjないしvirtual
    • オリジナルのRFCでは名前に関する議論はあまりなかった(動機の方が主関心)
    • そのため、特定のキーワードに対する合意や反論を書くのは不公平かもしれない
  • ただ、著者はdsynが良い選択だと考えている:
    • "dynamic"型付けは、広い範囲のプログラマに親しまれているので、誤解を招きにくい
    • objだとオブジェクト指向プログラミングの印象が強すぎ
    • virtualだと、そのキーワードが使われている言語以外のプログラマには親しみがなさそう
      • virtualをキーワードとして持つ言語でも「動的ディスパッチ」というよりは「このメソッドは上書き可能です」といった意味で使われる傾向がある

Object<Trait>のような過激な構文も可能ではあった:

  • オリジナルのRFCで提案はされたけど、あまり賛同は得られなかった
  • おそらく、キーワードに比べてノイズが多いし、誤解を招きやすい

bareトレイト構文を、トレイトオブジェクト以外の用途で再利用することができたかも:

  • implトレイトの方がトレイトオブジェクトよりも、bareトレイト構文に遥かに合っていると思われる
    • (このRFC内でも、implトレイトなら全てのトレイトと併用可能だし、実行時コストが無いことを示唆していた)
  • ただし、このRFCで用途変更の提案は行わない:
    • 非推奨と削除のみ
  • 著者は、bareトレイトの用途変更を行わずとも、dynトレイトには価値があると信じている
  • また用途変更には大きな欠点がある(i.e., エポック間でコードの互換性が大きく崩れる)
  • implトレイトとdynトレイトが安定化された後に、用途変更の議論がまたあるかも

未解決の問題

  • トレイトオブジェクトは実際のコード内でどの程度使われているか?
    • このRFCのオリジナルスレッドで質問があったが、今まで回答となるデータは提供されていない
  • この文脈的なキーワードの導入は、パースの曖昧性問題を引き起こさないか?
  • 将来的には、"The Book"の中で`implトレイト vs dynトレイト"をどう教えるか、を試しに書いてみるべき?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment