Rustに影響を与えた言語たち
Rustを使っていると時々「あれ?この機能、他の言語に似てない?」と思うことがあります。最初に思ったのはトレイトで、これはHaskellの型クラスやScalaのImplicitsを使った型クラスパターンと同等な機能と理解しました。クロージャのシンタックスはRubyのブロック記法に似ているなと感じました。そんな「似ている」を少しだけ深堀りしてみたいと思い、Rustに影響を与えた言語を調べて見ました。
TL;DR
- Rustに影響を与えた言語に関してはすでにまとまったページがありました
- この記事は上記のページをベースに以下のようにまとめ直して紹介しています
- 影響を受けた言語の特徴を表形式でまとめる
- 影響を受けた機能の簡単な紹介
- 影響の可視化(マインドマップ風)
Rustが影響を受けた言語
「Influences」に記載されている言語[^1]を年代順に並べて、言語の特徴をマトリックスにしてみました。特徴の選択はGCを除いてRustが力を入れているパラダイムを選択しています。また比較のためにRust自身も加えてあります。
登場年代 | FP | OOP | 並行計算 | 静的型付け | パラメータ多相 | アドホック多相 | GC | |
---|---|---|---|---|---|---|---|---|
C | 1972 | o | ||||||
Scheme | 1975 | o | o | |||||
C++ | 1983 | o | o | o | ||||
Newsqueak | 1984 | o | o | o | ||||
Erlang | 1986 | o | o | o | ||||
SML | 1990 | o | o | o | o | |||
Haskell | 1990 | o | o | o | o | o | ||
Alef | 1992 | o | o | o | ||||
Limbo | 1995 | o | o | o | ||||
Ruby | 1995 | △ | o | o | ||||
OCaml | 1996 | o | o | o | o | o | ||
ML Kit | 1997[^2] | o | o | o | △[^3] | |||
C# | 2000 | △ | o | o | o | o | ||
Cyclone | 2006 | o | o | o | △[^4] | |||
Rust | 2010 | o | o | o | o | o | o | |
Swift | 2014 | △ | o | o | o | o | o[^5] |
各カラムの意味は次のとおりです。言語の特徴は主にWikipediaを参考にしていますが、正確な分類は困難なため多少の独断と偏見が含まれていることをご了承ください。
- 登場年代
- プログラミング言語が登場した年代です。前後3年の誤差は見逃してください
- FP(関数型プログラミング)
- 言語がFPを強くサポートしているかを示しています
- 程々にサポートしている場合は△を示しています
- OOP(オブジェクト指向プログラミング)
- 言語がOOPを強くサポートしているかを示しています
- 並行計算
- アクターや CSP/π計算モデルの特徴を言語が強くサポートしているかを示しています
- 外部ライブラリを使えばできるよ!みたいなものは除外します
- 静的型付け
- 言語の最も主要な処理系が静的型付けをサポートしているかを示しています
- パラメータ多相
- 言語がパラメータ多相をサポートしているかを示しています
- ジェネリクス(Java)、テンプレート(C++)、let多相(ML系言語)等と呼ばれるものが含まれています
- アドホック多相
- 言語がアドホック多相をサポートしているかを示しています
- 型クラス(Haskell)、トレイト(Rust)、プロトコル(Swift)等と呼ばれるものが含まれています
- 単純な多重定義(オーバーロード)は含まれていません
- GC(ガベージコレクション)
- 言語の最も主要な処理系がガベージコレクションをサポートしているかを示しています
[^1]: Unicode Annexは言語ではないので除外しました。NIL, Hermesはすでに廃止された機能への影響だったので、これも除外しました。C言語はWhy Rust? - #Influences | Learning Rustに記載があったので追加しました。F#も入れるかどうか悩みましたが「Functional Programing」だけだと具体的に影響を与えた機能が分かりづらいので入れませんでした。
[^2]: Programming with Regions in the ML Kitの発表年を基準にしています。
[^3]: ML Kitの実装にはリージョンベースのメモリ管理にGCを付けたものもあるので△にしています。(参考文献)
[^4]: GCの利用はオプションなので△にしています。具体的にはヒープリージョンにガベージコレクションを利用できます。
[^5]: SwiftではARC(Automatic Reference Counting / 自動参照カウント)が使われています。ARCをGCに分類するかは議論の余地がありますが、この記事ではARCの実行時オーバーヘッドの存在を考慮して「GC」として分類しています。
Rustが影響を受けた機能
ここからは影響を受けた機能をそれぞれ見ていきたいと思います。
代数的データ型(algebraic data types)
代数的データ型は関数型プログラミングをサポートする言語で多く利用されています。直積型の総和です。RustではEnumを用いることで代数的データ型を実現可能です。
1 |
|
上記はIPアドレス型を表現していますが、V4型はu8型の直積になっており、V4型とV6型の総和がIpAddr型になっています。
- 影響を受けた言語
- SML, OCaml
パターンマッチ(pattern matching)
パターンマッチはざっくり言うと、if
やswitch
等の一般的な分岐構造の強化版です。Rustではmatch
でパターンマッチが利用可能です。
1 |
|
パターンマッチは上記のコードのようにデータ構造を分解してマッチングできるので、代数的データ型とも相性がよいです。
パターンマッチに関しては過去に「全プログラマに捧ぐ!図解「パターンマッチ」」という記事も書いたのでよろしければそちらも参考にしてださい。
- Rust本
- リファレンス
- 影響を受けた言語
- SML, OCaml
型推論(type inference)
静的型付けを持つ言語ではJavaやC#に採用されたこともあり、型推論が徐々に浸透してきています。Rustの型推論は強力なHindley-Milner型推論をベースとしており、後方の式や文から手前の型が推論されることもあります。
1 |
|
上記の例ではHashMapのキーと値の型が後続のinsert関数の引数から推論されています。JavaやC#の型推論ではこれはできません。それはこれらの言語に導入された型推論はローカル変数の型推論であり、Rustの型推論とは異なっているからです。
- 影響を受けた言語
- SML, OCaml
セミコロンの文区切り(semicolon statement separation)
Rustの関数は主に「文」で構成され、一番最後は「文」か「式」で終わります。Rustの文と文の区切りはセミコロン「;」です。従って最後にセミコロンが付くか否かで文か式かを区別できます。
1 |
|
Rustの面白いところは、全ての「式」の後にセミコロン「;」を付けるだけで「文」に変換できてしまうところです。そして文になると関数の最後に置かれた場合の戻り値はユニット()
になります。上記のコードで説明すると一番最後の行にセミコロン(;
)があるかないかで戻り値が変わります[^6]。つまりRustにおいては「文」も「値」を返す「式」の一種と考えることができます。また、if
やmatch
やwhile
のような制御構造も値を返すのでRustは式指向の言語とも言われています。
影響を受けた言語であるOCamlでも同様にプログラムの構成要素の基本に「式」を置いて、セミコロンで区切るやり方になっています。
- 参考文献
- 影響を受けた言語
- SML, OCaml
[^6]: 戻り値がユニット()
との場合、関数のシグネチャから戻り値を省略できます。つまりfn test2 -> () {...}
はfn test2() {...}
と同義です。
参照(references)
参照は変数に&
をつけることで生成でき、変数に「別名」を作ることができます。C言語のポインタに似た機能ですが、大きな違いはnullポインタが存在しないことです。つまり必ず参照先があることが前提となります。参照先は参照外し演算子「*
」を用いることで参照できます。可変(mut
付き)の変数の場合は参照先の書き換えも可能です。
1 |
|
この参照はC++の特徴的な機能と考えられており紛れもなくC++の影響と言えそうですが、大きな違いもあります。それはRustの「所有権とライフタイム」との紐づきであり、C++と異なりRustの参照がダングリング参照になることはありません。
- 影響を受けた言語
- C++
RAII(Resource Acquisition Is Initialization)
RAIIは直訳では「リソースの確保は(変数の)初期化である」になります。しかしこの概念をより適切に理解するためには「リソースの解放は変数の破棄である」と捉えたほうが真に迫っていると思われます。一般的なリソースの代表例がメモリであり、この場合は変数が初期化されるとメモリが確保され、変数が破棄されるとメモリが解放されます。このRAIIという表現はRustでは表立って出て来ませんが、「所有権」という考え方の中に取り入れられています。
1 |
|
上記のコードの例では変数の有効範囲は変数が初期化されてから、最も内側のスコープ(中括弧{}
で囲まれた範囲)の最後までです。JavaやRubyのようなガーベージコレクション(GC)を持つ言語では変数が破棄された後も、ガベージコレクタがメモリを回収するまでメモリが解放されません^7。また上記のコードの例ではメモリをリソースとしましたが、メモリ以外でもファイルのオープン、クローズ等の「利用」と「返却」に結びつけても構いません。実際にRustの標準ライブラリの中にはRAIIを利用したものが多くあります。
- Rust By Example
- 影響を受けた言語
- C++
スマートポインタ(smart pointers)
スマートポインタは、ポインタの一種で単にメモリアドレスを指し示すだけではなく付加的な機能を備えたもののことを言います。Rustにおけるスマートポインタとは標準ライブラリの型で言えばStringやVec
1 |
|
- Rust本
- 影響を受けた言語
- C++
ムーブセマンティクス(move semantics)
ムーブセマンティクスとはざっくり言うと、値を変数にアサインしたり、関数を引数に値渡ししたりするときに所有権の移動が行われることを言います。
1 |
|
上記のコードではlet s2 = s1;
がムーブセマンティクスになっています。Rustでは値の所有権は常にひとつに制限されているのでこのような動作になります。
単相化(monomorphization)
Rustのジェネリクスはコンパイル時にプログラム内で利用される具体的な型に展開されますが、これは「単相化」と呼ばれています。「単相化」によってコンパイル時に呼び出される関数が決定されるので静的ディスパッチになり、抽象化に伴う実行時の呼び出しオーバーヘッドがありません。
影響を受けたC++では、単相化はテンプレートのインスタンス化、特殊化として知られています。
- 影響を受けた言語
- C++
メモリモデル(memory model)
メモリモデルが意味するものはいくつか挙げられますが、この文脈おけるメモリモデルはマルチスレッド環境における共有メモリアクセスの一貫性に関するものになります。一般的にマルチスレッドから安全に操作できるものとして「アトミックな操作」がありますが、これらを実現するためにメモリモデルが必要になってきます。詳細が知りたい方は以下のRustのドキュメントを参照してください。
- 標準ライブラリドキュメント
- 影響を受けた言語
- C++
リージョンベースのメモリ管理(region based memory management)
リージョンベースのメモリ管理では、メモリを「リージョン」と呼ばれる領域に分割して、さらに型システムに関連付けてメモリ管理を行います。Rustにおいては参照のライフタイム管理に大きく関わっているものと思われます。
- 参考文献
- 影響を受けた言語
- ML Kit, Cyclone
型クラス(typeclasses)、型族(type families)
「型クラス」はHaskell由来の言葉でRustで対応する機能はトレイトになり、型に共通する振る舞いを定義するときに用いられます。Javaのインタフェースに近いものがありますが、型の定義時ではなく型の定義後に後付でトレイトを実装できることが特徴です。
1 |
|
上記のコードで説明すると、print_greet()
関数はトレイトGreeting
を実装していれば呼べる関数になっています。そしてすでにJapanese
という型が定義されていた場合、Greeting
トレイトを実装(impl Greeting for Japanese
)すれば、print_greet()
関数で呼び出すことができます。面白いのはi32
のような組み込み型にも後付でトレイトが実装できることです。このprint_greet()
関数のように、後付けで渡せる型を増やせる関数の性質をアドホック多相性と言ったりします。
「型族」は、ざっくり説明すると型を受け取って型を返す型関数を実現する機能です。Rustでは「関連型」と繋がりがあります。標準ライブラリのAdd
から定義と利用例を引用します。
1 |
|
関連型は上記のコードのように、トレイトの中でtype
を使って宣言されます。ジェネリクスと似たようなこともできますがきちんと使いどころもあります。以下の参考文献に細かなシチュエーションが載っていたので興味がある方はご確認ください。
- 関連型の参考文献
- 影響を受けた言語
- Haskell
チャネル(channels), 並行性(concurrency)
チャネルは非同期コミュニケーションのためのプリミティブです。送信側と受信側がチャネルを通して非同期にデータの受け渡しをすることができます。以下のコードはチャネル - Rust By Example 日本語版からコード部分を引用したものです(コメントは独自のものに変更)。
1 |
|
上記のコードはチャネルを生成して子スレッドに渡して、子スレッドからチャネルを通してデータを送信して親スレッド受け取るコードになっています。
- 影響を受けた言語
- Newsqueak, Alef, Limbo
メッセージパッシング(message passing), スレッド失敗(thread failure)
調べきれなかったので割愛します。
- 影響を受けた言語
- Erlang
オプショナルバインディング
オプショナルバインディングはSwiftの機能で、その名の通りOptionalの値が存在する場合に変数を束縛してコードブロックを実行します。Rustの対応する機能はif let
ですが、Option
に限らず様々なパターンマッチか利用可能です。
1 |
|
- Rust本
- 影響を受けた言語
- Swift
衛生的マクロ(hygienic macros)
衛生的マクロとはマクロ内で導入される変数名と、マクロ呼び出し側の変数名が衝突しないことが保証されているマクロです。以下は簡単なRustのマクロのサンプルコードです。
1 |
|
Rustのマクロは衛生的なため、my_macro!
マクロに変数a
が渡されても内部のletで導入された変数a
とは別物とし扱われます。これがC言語のマクロやLispマクロでは衝突する可能性があるため、意図的に衝突しない変数を選ぶ必要がありました。
- 影響を受けた言語
- Scheme
属性(attributes)
属性は主に宣言に対して付加される追加情報(メタデータ)です。Rustでよく見かけるのは単体テストのマークとなる#[test]
属性です。
1 |
|
- リファレンス
- Rust By Example
- 影響を受けた言語
- C#
クロージャー記法(closure syntax)
これはRubyのブロック記法とRustのクロージャ記法を見比べて貰えば似ていることがおわかり頂けると思います。
1 |
|
1 |
|
- 影響を受けた言語
- Ruby
影響の可視化
Rustに影響を与えた言語を可視化してみました。言語は年代順に時計回りでざっくり並べています。色はFP,OOP,並行計算,その他でざっくり分類しています。
この図を見ると、様々なパラダイムの言語からバランス良く影響を受けている様子が見て取れます。
まとめ
Rustに影響を与えた言語についてざっくり表に分類して、さらに可視化してみました。また、影響を与えた個々の機能に関しても大まかに紹介しました。元ネタになったRustリファレンスのInfluencesの記載は以下のとおりです。
- SML, OCaml: algebraic data types, pattern matching, type inference, semicolon statement separation
- C++: references, RAII, smart pointers, move semantics, monomorphization, memory model
- ML Kit, Cyclone: region based memory management
- Haskell (GHC): typeclasses, type families
- Newsqueak, Alef, Limbo: channels, concurrency
- Erlang: message passing, thread failure,
linked thread failure,lightweight concurrency - Swift: optional bindings
- Scheme: hygienic macros
- C#: attributes
- Ruby: closure syntax,
block syntax - NIL, Hermes:
typestate- 削除された機能のみに紐づく言語なので本記事では扱わなかった
- Unicode Annex #31: identifier and pattern syntax
- 言語ではないので本記事では扱わなかった
Rustは一見すると多くの先進的な機能が詰まっているように見えますが、その多くは研究成果や実績のある言語を下敷きにしていることが分かります。Rustの特徴としてよく話題になる所有権システムや借用チェッカーでさえもC++,ML Kit,Cyclone等の言語から多くの影響を受けています。そして個々の影響を調べると、ML KitやCycloneで成し遂げられなかった完全なGCとの決別をRustで実現できたという流れも見えたのは面白かったです。
自分も調べてみるまで、ここまで多くの機能が他の言語由来だとは思っていませんでした。影響を受けた言語の年代やパラダイムもバラエティに富んでおり、Rustを調べている内にさながら言語の進化の歴史を学んでいるような感覚に陥りました。そしてRustはそれぞれの言語の欠点をうまく乗り越えて、良い点をうまく統合していく過程を見せてくれたような気がします。
確かにこれだけの言語から影響を受ければ初学者にとって学習曲線が急峻だと言われるのも分かりますが、逆を考えると影響を受けた様々な言語の集大成を一つの言語で学べる非常に学びがいがあるお得な言語だと言えるのではないかと、これを書きながら思った次第です。
本記事がRustに興味がある方々の一助になれば幸いです。
参考文献
- Influences - The Rust Reference
- Why Rust? - #Influences | Learning Rust
- C言語 - Wikipedia
- Scheme - Wikipedia
- C++ - Wikipedia
- Newsqueak - Wikipedia
- Erlang - Wikipedia
- Standard ML - Wikipedia
- Haskell - Wikipedia
- Alef (programming language) - Wikipedia
- Limbo - Wikipedia
- Ruby - Wikipedia
- OCaml - Wikipedia
- C Sharp - Wikipedia
- Cyclone (programming language) - Wikipedia
- Swift (プログラミング言語) - Wikipedia
- Rust (プログラミング言語) - Wikipedia
- Programming with Regions in the ML Kit
- Rustの型推論の概略 - 簡潔なQ
- メモリモデル (プログラミング) - Wikipedia
- Rustの
Arc
を読む(4): アトミック変数とメモリ順序 - Qiita