概要
2011~2013くらい(Rust ver 0.1 - 1.0くらい)の黎明期Rustにおける「トレイト」システムの導入経緯を、当時のMozillaのメーリングリスト等のweb上に残存する記録から簡単に辿ったもの。
目的
特になし。別記事を書くためにRustのトレイトまわりの導入経緯について調べていたらボリュームが多くなってしまったので、メモ書き程度にまとめたもの。Rustの開発者たちがどういう意図で現在の「trait」を設計したのか、そのあたりの情報にアクセスする足掛かり程度にはなるかもしれません。
筆者はweb上に残っている記録を拾っただけの素人です。「当時を知る人」でも「コンピューターサイエンスに精通する人」でもないので、もし誤りや漏れ等を見つけた方がいれば、ご指摘を頂けると有難いです。
Object system redesign
私が見つけた範囲において、Rustの文脈でトレイトが言及された最も古い記録は、Rust 0.1リリースの約1年前、2011年11月にMozillaのメーリングリストに立てられたObject system redesign(オブジェクトシステムの再構築)」と題されたスレッドです。1
このスレッドを立てたPatrick Walton氏は、それまでのRustのオブジェクトシステムにおける「表現力や開発体験に関わる非常に現実的な問題」として、以下の8点を挙げています。
- 全てのメソッドコールが仮想的である
- 全てのオブジェクトがヒープ上に格納される
- プライベートメソッドが存在しない
- selfが第一級オブジェクトでない
- パブリックフィールドがない
- 単一継承しかできない
- オブジェクトの拡張に、双方向のvtableの構築が必要
- 型システムが名前的(nominal)でなく構造的(structural)であるため、再帰的になれない
そしてこれらの問題の解決として、class
, interface
, そしてtrait
の3要素を柱とする新たなオブジェクトシステムを提案しています。これら3つの使い分け方は
- ポリモーフィズム(多相性)が必要な場合は、
interface
を使う - コードの再利用が必要な場合は
trait
を使う - どちらでもなければ、単に
class
を使う
とされており、現在のRustにおけるトレイトの役割がinterface
とtrait
に分割されていたようなイメージでしょうか。
この時点では、Rustの生みの親であるGraydon Hoareは、「強力なプロポーザルだ」と評しつつも、多くのユーザーを対象とするにはあまりに「heavy」だとして慎重意見を出しています。2
その後categoryシステムやインターフェース/型クラスシステムのプロポーザルが出されたりしていますが、翌2012年の1月の時点でinterface
の実装が進んでいることが報告されています。3 インターフェースをiface
キーワードで定義し、impl of [interface] for [type]
でメソッドを実装するという形だったようですね(下記)。
impl of iter_util<T> for [T] {
fn len() -> uint { std::vec::len(self) }
fn iter(f: block(T)) { for elt in self { f(elt); } }
fn map<U>(f: block(T) -> U) -> [U] {
let rslt = [];
for elt in self { rslt += [f(elt)]; }
rslt
}
}|
Say that with a traitface
その後の半年感ほど経過を見つけることが出来なかったのですが、2012年7月にGraydon Hoareが新たなロードマップを提示したことが告知されます。4 その内容が以下の通りです。
今週頭にGraydonが送った開発ロードマップの中で、2つの変更が提示されました。「インターフェイスを完全なトレイトへと拡張すること」と「実装のコヒーレンスを強制すること」です。 これらは、traitとinterfaceを統一するこのProposalによって扱われます。最近、IRCやTwitterでたまに「traitface」という言葉が使われているように、この作業はすでに始まっています(安心してください!私たちは実際にそう呼ぶつもりはありません)。
興味深い表現がいくつか出てきました。まず、「traitとinterfaceを統一」ですが、リンク先のGitHub issueにより詳しい内容が書かれています。
おおまかな内容は以下の3つです。
-
iface
にデフォルト実装をできるようにする -
iface
を合成可能(composable)なものにする -
impl
にコヒーレンスを強制する(1つのiface
と型のペアにつき、1つの実装しか許さない)
インターフェースをもとに、トレイト由来の機能を足していっている雰囲気があります。5
加えて、「traitface」という言葉が出てきました。あくまでも仮称のようですが、Dave Herman氏曰く、この時の新しいRust言語のモットーは "say that with a traitface(それ、traitfaceで書こうよ)"だったそうで、traitでもinterfaceでもなく、それらを足し合わせたものだ、という意識の強さを感じます。(それにしてもすぐに「いやいや勿論こんな名前で呼ぼうとは思ってないから安心してくれよ」みたいなことが書いてあるあたり、ネイティブ的にはtraitfaceってのはよっぽどダサい表現なんですかね?)
実際、"trateface"という呼称は使われることはなく、その後iface
キーワードがtrait
で置き換えられ、その後の発展を経て現在の形へとつながっていったようです。
結論
"traitface"という言葉が出てきたあたり、Rustの開発者たちはRustのtraitをtraitのつもりで作った訳ではないのか?という雰囲気を感じた一方で、このあたりの用語の定義範囲は人によって差がある印象も受けました(これ以上は私の実力では考察不可)。
冒頭で述べたRust公式チュートリアル10.2章ではインターフェースを基にした説明がなされていて、入門者向けにやさしい比喩を使っているのかな、と思っていましたが、上述の経緯を見る限り、案外額面通り受け取っても良いのかもしれません。「メソッドのデフォルト実装を書けるインターフェース」程度の理解でも、問題になる場面はあまり無いのではないでしょうか。
-
https://mail.mozilla.org/pipermail/rust-dev/2011-November/000929.html ↩
-
https://mail.mozilla.org/pipermail/rust-dev/2011-November/000933.html ↩
-
https://mail.mozilla.org/pipermail/rust-dev/2012-January/001179.html ↩
-
https://mail.mozilla.org/pipermail/rust-dev/2012-July/002042.html ↩
-
この時点では、トレイト内にフィールドを持つか持たないか(いわゆるstatefulかstatelessか)は決まっていなかったようです。一応RFCはありますが: https://github.com/rust-lang/rfcs/pull/1546 ↩