概要
Rustのトレイト実装の記法impl トレイト名 for 型名
が(個人的には少し風変りな文法に見えたので)なぜそのような形をしているのか、文法実装者本人の記述等から考察を行った記事。
Rustのトレイト実装記法について気になった点
Rustに入門してはじめてトレイト実装の章を読んだ時、その記法に対して以下の点が気になりました。
1. impl A for B
とimpl B
で目的語が変わる
日英ともに公式bookでは、impl 型名 {メソッド定義}
の意味を「型にメソッドを実装する」のように説明しており、この場合はメソッドが目的語・型名が間接目的語になります。一方impl トレイト名 for 型名 {メソッド定義}
の意味は「型にトレイトを実装する」と説明されており、トレイト名が目的語・型名が間接目的語になります。「トレイトに沿っているかどうかは別として、どちらの文法も型に対してメソッドを定義している」という考えれば、わざわざ目的語が変わる文法にするのは違和感を覚える人もいるでしょう。実際そのような印象を持った人はいたようで、2014年頃に別文法を提案している投稿を見つけることが出来ます。
https://www.reddit.com/r/rust/comments/2kga3a/implfor/
https://www.reddit.com/r/rust/comments/299u9m/impl_traittype_ordering/
2. 前置詞がfor
英会話で「AにBを実装する」と言う時、よく聞くのは「implements A on B」または「implements A to B」です。実際、英語の公式Rust bookの該当章では、impl トレイト名 for 型名
の意味を説明するのに「implement trait on types...」という英文を使っています。「implements A for B」も不自然な英文ではありませんが、何らかの意図を感じる前置詞チョイスです。
3. トレイトが型の左に配置される文法
代表的なプログラミング言語における「クラス(型)にインターフェース(トレイト・プロトコル・抽象型)を実装する記法」を以下に示しました。クラス名: インターフェース名
かインターフェース名 implements クラス名
の形が多く、インターフェース側の単語がより目立ちやすい位置(左側)に配置される記法は珍しいです(RustとHaskellくらい)。
言語名 | 記法 |
---|---|
C++ | class クラス名: インターフェース名 {} |
C# | class クラス名: インターフェース名 {} |
D | class クラス名: インターフェース名 {} |
Kotlin | class クラス名: インターフェース名 {} |
Java | class クラス名 implements インターフェース名 {} |
TypeScript | class クラス名 implements インターフェース名 {} |
PHP | class クラス名 implements インターフェース名 {} |
Visual Basic | Class クラス名 Implements インターフェース名 ... End Class |
Dart | class クラス名 implements 抽象クラス名 {} |
Swift | struct クラス名: プロトコル名 {} |
Objective-C |
@interface クラス名: NSObject<プロトコル名> @end @implementation クラス名 ... @end
|
Scala | class クラス名 extends トレイト名 {} |
Rust | impl トレイト名 for 型名 {} |
Haskell | instance 型クラス名 型名 where ... |
F# | type クラス名() = interface インターフェース名 with ... |
Julia |
struct 型名 <: 抽象型名 end メソッド名(obj::型名) = ...
|
Go | 型にインタフェースメソッドをすべて実装する |
文法設計者の意図
実は2012年までのRustは(多くの言語と同じように)impl 型名: トレイト名 {}
の構文を持っていました。impl トレイト for 型 {}
の構文が導入されたのは2013年の1月で、Rustにトレイト自体を導入したPatrick Walton氏のコミットによって実装されています。
このコミットに直接関連する議論はweb上で見つけられませんでしたが、Walton氏が自身のブログ(現在は削除済み)内で導入の意図らしきものを説明しています。
興味深い内容だったので、以下ポイントを述べます。
1. (当時は)メソッドは型ではなくトレイトにスコープされる
型にメソッドを定義した再、メソッドの呼び出しはインスタンス名.メソッド名()
のように使うことが多いと思いますが、Rustでは他にも以下(2)~(6)のように「Plain path 記法」や「Qualified path 記法」といった呼び出し方があります。
trait Tr {
fn method(&self) -> ();
}
struct St;
impl Tr for St {
fn method(&self) {
println!("Trait method");
}
}
fn main() {
let s = St{};
// インスタンスからのメソッド呼び出し
s.method(); // (1) -> Trait method
// Plain path 記法
St::method(&s); // (2) -> Trait method
Tr::method(&s); // (3) -> Trait method: 2013年当時はNG
// Qualified path 記法
<St>::method(&s); // (4) -> Trait method: 2013年当時はNG
<St as Tr>::method(&s); // (5) -> Trait method: 2013年当時はNG
<dyn Tr>::method(&s); // (6) -> Trait method
}
インスタンスの実態がメソッドの第一引数であることを考えると、(2)~(6)はプログラムの実動作に近い呼び出し方ですね。現在のRustでは(1)~(6)がすべて合法ですが、2013年当時のRustでは(3)~(5)、つまり型名からの呼び出しは出来ませんでした。Walton氏はこれを根拠として、文中で「メソッドは型のものではなくトレイトのものである」と明言しています。
2. impl 型名 {}
は「匿名トレイト」の実装である
impl 型名 {}
記法についても面白いことが書いてあります。
Earlier I was calling this an “anonymous trait”, but I think that this terminology is probably more confusing than it’s worth.
以前、私はこれを「匿名トレイト」と呼んでいた。こういう用語は、それ自体価値こそあるだろうが、おそらくそれ以上に混乱を招くだけだろう(だから使わない)
通常のメソッド定義は名前を持たない「匿名トレイト」の実装と捉えられる、という考えです。Walton氏としては、impl 型名 {}
はimpl _ for 型名 {}
の省略系のようなイメージだったのかもしれません。
語順の意図の推測
以上のように、(少なくとも初期の実装者の言葉からは)トレイトこそが主役なのだという思想が強く感じられ、これがトレイトが目立つ文法に反映されたと私は考えています。前置詞がonやtoではなくforなのも「トレイトを実装して型に取り付ける」のではなく「型に合わせてトレイトを実装する」というイメージの表れではないでしょうか。
とはいえ、前述したとおり、現在のRustでは型名からメソッドを呼び出すことも可能ですし、様々なパラダイムで捉えることが出来るでしょう。敢えて言うなら、トレイト(動作)と型(データ)は独立に存在していて、それらが交わるところに実装が発生する、というイメージかもしれません。いずれにせよ、〇〇の言語は〇〇のパラダイムで捉えるべきである、と主張することがこの記事の意図ではありません。
おわりに
文法というものは覚えてしまえばそれまでで、その意図や起源を探るというのはあまり生産的な行為では無いでしょう。ただ気になる時は気になってしまうもので、この記事が私と同じような誰かの時間の節約になれば幸いです。