- Start Date: 2017-08-17
- RFC: http://rust-lang.github.io/rfcs/2113-dyn-trait-syntax.html
- PR: rust-lang/rfcs#2113
- Issue: rust-lang/rust#44662
- トレイトオブジェクトのために
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トレイトの導入で望ましいイディオムが変わる:
- 現状だと、名前無しであるトレイトの実装を返そうとすると、通常
Box
でトレイトオブジェクトを包むことになる - クロージャ、イテレータ、フューチャー、結合子、等を返す関数の大半がこれに該当する
- => これらは(安定化されたら)implトレイトに切り替えるべき
トレイトシステムをを教える方法にも変更が必要:
- implトレイトを説明する際には、ジェネリクスやトレイトオブジェクト等のトレイトの既存の利用法と合わせて行う必要がある
- それら(やenumのような類似機能)ではなくimplトレイトを使う方が適切なケースについても書く
- クロージャやイテレータについて教える際には、implトレイトがなぜ有用か、を多くの例と共に伝える
- 同様にimplトレイトでは不十分で、dynトレイトが必要なケースにも言及
つまり(理想的には):
- implトレイトを導入するなら、いずれにせよいろいろと変更は必要
- dynトレイトをさらに追加しても、あまり余計なコストは掛からない
専用キーワード:
- impl/dynキーワードは、static/dynamicディスパッチの関係を素直に反映している
- 両方のディスパッチ方法に、専用のキーワードがあることで、その差異や選択の重要性が適切に示唆される
- 現状の構文だと「トレイトオブジェクトがデフォルト」でimplトレイトはよりニッチな機能だという印象を与えかねない
誤用時のケア:
- implトレイトが安定化したら、間違えてトレイトオブジェクトを書いてしまうことは普通にありそう
- e.g.,
impl
キーワードの記述漏れ
- e.g.,
- こういったケースでは、不親切で難解なエラーメッセージが出力されがち
- 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トレイト"をどう教えるか、を試しに書いてみるべき?