Rust

Rust

Mozillaによるシステムプログラミング言語。
みんなから愛されて、2016-2017年のStack Overflowで「最も愛されているプログラミング言語」で一位を獲得している。

特徴

プログラミング言語 Rustからのコピーです。
・ゼロコスト抽象化
・ムーブセマンティクス
・保証されたメモリ安全性
・データ競合のないスレッド
・トレイトによるジェネリクス
・パターンマッチング
型推論
・最小限のランタイム
・効率的なCバインディング

Rustの根本原理はエンパワーメント

Rustの根本原理は、エンパワーメント(empowerment)にあります。
これは、「プログラマとしてしっかりと活躍するための能力を与える」ということを意味しています。
僕が思うに、たしかにRust自体がとても優れた神のような言語であり、最高のツールである、ということも正しいですが、それだけではなく、「Rustを学ぶことで一流のエンジニアとして活躍できるようになるための基礎的能力を得られる」という側面が強い言語ではないかと思います。
Rustで本当にプログラミングを行うこともできますが、それよりもRustは「力を与えてくれる」のです。
なので、以下のRust Bookのsecond-editionの最初にも、エンパワーメントの原則が登場します。この本は難しいように見えて本当は簡単であるため、時間のある時に流し読みをしましょう。

Rustは、「今まで職人や限られた技術者にしかできなかった低レベルなレイヤーの処理で、古い制限や間違いを起こしやすい原因を撤廃し、きちんと正しいやり方で書くことができやすくする」といったことを目的としています。もちろんWebAssemblyなどを使うことで、Web向けにもシステムを構築できます。

面白い要素が満載

Rustには、言語として面白い要素が満載です。たとえば、同じ名前の変数を覆い隠して命名することができ、一度let x = 2;と不変変数を束縛した場合、このxをx = 3;と変更することはできない(これをするためにはmutを明示的に宣言して可変変数にしなければならない)ですが、もう一度変数xをlet x = x + 1;と「同じ名前で新しい宣言を行う」ことはできます。
例外の処理やmatch式なども独自で面白いですし、所有権や参照・借用や寿命の概念によるGCの要らない安全でハイパフォーマンスなメモリ管理や、安全な並列プログラミングができる、というところは、「本当にRustを使ってやろう」という気を起こさせてくれます。ムーブセマンティクスの概念は、最初は難しいですが、「値でなく所有権を渡す」と思えば理解できます。
構造体とタプルもあり、構造体はインスタンス化できます。また、トレイトの考え方や「型に対して追加でメソッドを実装する」という考え方は、単なるクラス継承による拡張性をさらにエレガントに超越しています。標準の数値型にすら新しいメソッドを簡単に実装できます。
またCargoという開発ツールがバンドルされており、最初から依存関係を高度にこなしてくれるパッケージ管理システム兼ビルドツールとして利用できます。
また、言語的に見ていると、「やり方がC++の全面的改良版」であるという特徴があり、たとえば入力処理などでも関数に参照(ポインタと同じ)を渡すなど、今までC++でやってきたやり方(たとえばWindowsなどでのポインタの使い方)とよく似ています。全体的に見てもC++に似ているため、C++エンジニアにとってみれば「ベターC++」のように使うことができます。
こういう「言語的に面白い、新しいアイディアが満載」であるという特徴があるため、「自分の言語を設計したい」という言語設計者にとっても知っておくべき言語ではないかと思います。

トレイト

Rustには、クラスやプロトタイプチェーンはなく、代わりにトレイトを使ってオブジェクト指向を実現する。
トレイトはメソッドを集めたもので、これを特定の型に実装することで、その型の値がメソッドを提供するようになる。
Rustではクラスのように、型とメソッドは最初から一緒のものとしてまとめられていない。特定の型に対して、後からメソッドを「実装」する。
また、Rustでの継承などのオブジェクト指向は、トレイトによる「ミックスイン」によって提供されている。

所有権

Rustの変数では、値そのものを渡す(コピー)のではなく、値の所有権を渡す(ムーブ)のが標準。
Rustの所有権は、以下の特徴を持つ。
1.変数を束縛(代入)すると元の変数にはアクセスできなくなる(所有権はひとつだけ)。
2.その代り、参照はいくらでも作れる(借用)。
3.値を変更できるミュータブルな参照はひとつだけ。
この特徴の下、スタックに変数を置くことで、ガベージコレクションなしでリソースを自動的に開放できる。
後日注記:すなわち、データをコピーするだけでも所有権は移動し、もとの変数は使えなくなる。例外はCopyが用意されている基本的データ型だけ。ただし、参照はいくらでも作ることができるため、関数呼び出しの際には参照を与えることで、所有権を移動させることなく同じデータを参照できる。また、値を変更できるミュータブルな参照はその時その時ひとつだけであるため、関数に参照を変えてほしい時はミュータブルな宣言をして参照を渡せばいい。

神のようなメモリ管理

Rustでは、所有権と寿命の概念に基づいて、GCガーベッジコレクション)を行わなくても寿命以上変数が生き残る術は最初から存在しない。そして、参照はたくさんあっても、値を変更できるミュータブルな参照はひとつだけである。これによって、多くの場合、ほとんど絶対にメモリ破損が起きない。それは並列性を意識したプログラミングで有効であり、同時にGCを行わないためにC言語ほどにスピードが速く、効率的である。
まさに、神のようなメモリ管理である。最初から寿命をはっきりさせ、所有権をブロックや関数と変数の間で明確にすることで、寿命以上の参照を行おうとすると、コンパイルエラーが出る。
後日注記:実際は寿命をどれだけ伸ばすのか、ということは自分で管理することもできる模様。僕はまだそこまでBookを読めていない。

Cargoでの開発

まず、ビルドシステム兼パッケージマネージャのcargoでプロジェクトを作る。

$ cargo new hello_cargo --bin
$ cd hello_cargo

src/main.rsは以下のようになる。

fn main() {
    println!("Hello, world!");
}

あとは、

$ cargo run

でOK。自動的にビルドして実行してくれる。

自分で作った簡単な計算プログラム

自分で作った簡単なプログラムです。攻撃力が10のキャラと、攻撃力が1だが「1ターンごとに攻撃力が1上がっていく」キャラが戦って、いつ上がっていく方のキャラが上回るか、を計算する。
正確にいうと、「いつ上回るかを計算する」というより、その「上回っていく過程」を表示する。

fn main() {
  let mut x = 10;
  let mut y = 1;
  let mut count = 1;
  loop {
    print!("{} ", x);
    println!("{}", y);
    x += 10;
    count = count + 1;
    y += count;
    if (count > 100) {
      break;
    }
  }
}

少し変えたバージョン:

fn main() {
  let mut x = 10;
  let mut y = 1;
  let mut power = 1;
  while (power <= 100) {
    println!("{} {} {}", x, y, power);
    x += 10;
    power = power + 1;
    y += power;
  }
}

簡単な説明

その他の言語

  • その他の言語
    • Rust
      • ムーブセマンティクス
      • 寿命
      • 所有権、参照、借用
      • トレイト
      • 所有権はひとつだけ
      • 参照はいくらでも作れるが、ミュータブルな参照はひとつだけ
    • Golang
      • goroutine
    • Erlang, Elixirなど

所有権について

まず、ヒープに確保される型については、束縛した時にムーブされ、もともとあった変数は使えなくなる。これはメモリ解放の二重化を防ぐ。明示的に変数のクローンを作りたいときはcloneメソッドを読みだす。
しかしながら、Copyの型については、コピーされる。これはスタックに積まれる基本型の場合が多い。数値などはCopyであるため、もともとの変数を使うことはできる。
所有権はひとつだけであり、そのため関数の呼び出しの際の引数や返り値であっても、所有権が移動するため、もとの変数は使えなくなる。
しかしながら、これではやりづらい。そのため、参照が用意されている。関数の引数に参照を与えることで、いろんな関数から変数を参照でき、所有権はもとの変数にあったままにできる。関数の引数として参照を与えることを借用と呼ぶ。
参照はいくらでも作ることができるが、通常参照は不変である。
関数の中から変数を書き換えるために参照を使う場合、ミュータブルな可変の参照を使う。この際、ミュータブルな参照はその時その時に必ずひとつしかないと決まっている。この制約により、データの競合を防ぐ。