Rustに入門してみる

今日は、Rustに入門してみる。

Rustは、Mozillaの開発したプログラミング言語である。公式サイトでは"high-level, bare-metal programming"を謳っており、高い抽象度を保ちながら低いレイヤーのコントロール(具体的にはユーザーによるメモリ管理等)が可能らしい。

近年出てきた言語の内、高い抽象度とハイパフォーマンスの両立を強みにしていた言語としてはGo言語が真っ先に思い浮かぶ。ただ、GoにおいてはGCを採用していた事を考えると、手動でメモリ管理が可能なRustはさらに低レイヤーを担当する事を目指してるようだ。

MozillaとしてはCやC++に置き換わりたい様だし、そういった立ち位置を目指す言語に興味が湧いたのでちょっと触ってみた。

とりあえず、公式サイトのGuideの手順を順番に試してみる。

1. RustをMacにインストール

公式サイトに従って、curlでschellscriptダウンロードして実行する。

curl -s https://static.rust-lang.org/rustup.sh | sudo sh

rustとcargoのinstallerのダウンロードが始まって、だいたい5~10分程でインストールが完了する。

ちなみに、installされたRustのversionはrustc --versionで確認出来て、14/12/06時点では以下だった。

rustc 0.13.0-nightly (6f4c11be3 2014-12-05 20:23:10 +0000)

まだ1.0前のアルファ版なのでアップデートはあるらしく、定期的にcurl -s https://static.rust-lang.org/rustup.sh | sudo shでアップデートしてねって感じらしい。

2. Hello worldしてみる。

とりあえずHello world。main.rsファイルにmain関数を定義。

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

rustcコマンドでコンパイル。そして実行

$ ls
main.rs
$ rustc main.rs
$ ls
main    main.rs
$ ./main
Hello, world!

ちゃんとHello worldが出力された。やったぜ!!

main.rsのコードについて

割と見たまんまの解釈で良くて、fnキーワードと波括弧({})で囲んだブロックでmain関数を定義してる。main関数は特別な関数で、生成したバイナリの実行時にはmain関数の中身が実行される(この辺はCとかと一緒)。

println!ってのが実はただの関数じゃなくて、Macroらしい。ビックリマーク(!)ついてればMacroらしいけど、まだ序盤だからって事で理由は教えてくれなかった。そもそもRustにおけるMacroってのが何を指してるのかはまだ分からない。

後、indentはスペース4つって指定されてた。

3. Cargoを使う。

CargoというのはRustのプロジェクトを管理する為のツールで、具体的にはRustのコードのビルド、依存ライブラリのダウンロード、依存ライブラリのビルドの3つの仕事を行うらしい。そこそこの規模のプログラムを書くならCargoを使うべきとの事。

まあ今は練習なので、先ほど書いたmain.rsをCargoプロジェクトにしてみる。まずは、srcディレクトリを作ってそこにmain.rsを移動する。cargoでは、srcディレクトリにコードを置くのが規約らしい。

$ mkdir src
$ mv main.rs src
$ rm main

次に、configuration fileであるCargo.tomlを生成。TOMLって見た事なかったんだけど、INIの進化版みたいな感じらしい。

// Cargo.toml
[package]

name = "hello_world"
version = "0.0.1"
authors = [ "south37 <sout37777@gmail.com>" ]

[[bin]]

name = "hello_world"

Cargo.tomlにはプロジェクト名、version, author名なんかを書く。そして、buildコマンドでビルド。

$ cargo build
Compiling hello_world v0.0.1 (file:///Users/<ユーザー名>/Documents/programs/rust/hello_world)

$ ./target/hello_world
Hello, world!

これで、targeフォルダ以下に実行ファイル(hello_world)が生成される。良い感じ!!

ちなみに、cargo build実行後にはCargo.lockファイルが生成される。中身は

[root]
name = "hello_world"
version = "0.0.1"

で、あまり大した内容になってないけど、依存ライブラリ等があればここに書き込まれていくらしい。

lockって拡張子がGemfile.lockを彷彿とさせるんだけど、どちらも元ネタは同じだったりするのだろうか?gemはまさに依存関係管理ツールだし。

4. 変数へのbinding

Rustでは、letキーワードを使って変数への値のbindingを行う。Rustは静的型付け言語であり、型付けを行わなければならない。

let x: int = 5;

型の表記が無いとエラーが出る。

let x = 5;
$ rustc practice.rs
practice.rs:2:9: 2:14 error: cannot determine a type for this local variable: cannot determine the type of this integer; add a suffix to specify the type explicitly [E0102]
practice.rs:2     let x = 5;
                      ^~~~~
error: aborting due to previous error

ただ、Rustは型推論をサポートしている為、型が自明であれば問題は無い。例えば、上記の例ではiをつけて数値がintegerである事を示してあれば大丈夫。

let x = 5i; // 5の後ろのiがintegerの値であることを示している。

デフォルトでは変数はimmutableであり、書き換えることは出来ない。

let x = 5i;
x = 10i;
$ rustc practice.rs
practice.rs:3:5: 3:12 error: re-assignment of immutable variable `x`
practice.rs:3     x = 10i;
                  ^~~~~~~

mutableな変数を用意したければ、mutキーワードを使う。

let mut x = 5i;
x = 10i;

immutableがデフォルトってのは個人的にはとても好ましい。公式サイトによれば、安全性を考慮した結果らしい。

5. if構文

Rustのifを使った条件分岐は見たまんま。自然に理解できる。

let x = 5i;

if x == 5i {
    println!("x is five!");
} else {
    println!("x is not five :(");
}

ただ、Rustにおいては構文のほとんどが式(expression)であり、値を返すため、if式も下みたいな使い方ができる。

let x = 5i;

let y = if x == 5i { 10i } else { 15i };

良い感じ。

Rustにおける構文の内、値を返さない文(statement)は2種類しか無いらしい。一つは変数のbindingで、下記の表記は出来ない。

let x = (let y = 5i); // expected identifier, found keyword `let`

もう一つはexpression statementと呼ばれる構文で、セミコロン(;)をつけることでexpressionがstatementに変換される。(逆に言えば、デフォルトではrustの構文は全てがexpression)。

例えば、if式の中でセミコロン(;)を使うと値が返されない(より正しく言うとunitと呼ばれる特別な値が返される)。その為、以下のセミコロンを用いたif構文の結果を変数にbindingしようとするとエラーが出る。

let x = 5i;

let y: int = if x == 5i { 10i; } else { 15i; };
$ rustc practice.rs
practice.rs:4:18: 4:51 error: mismatched types: expected `int`, found `()` (expected int, found ())
practice.rs:4     let y: int = if x == 5i { 10i; } else { 15i; };

終わりが見えてこない

まだ構文の本当に基礎っぽいとこしか触れてないけど、終わりが見えてこないので今日はここまで...

本当はRustのメモリ管理に興味があって、ownershipとかborrowingとかみたいな現代的なメモリ管理について学べそうで興味を持ったんだけど、このペースだとたどり着けないのでまた今度にする。

ownershipは、まだあんま分かってないけどスコープを抜けるときに確保したメモリが解放される仕組みっぽい。それだけだとスタックと変わら無いんじゃないかと思うけど、別のスコープにownershipを移す事ができたり(その場合は元のスコープの所有では無くなる)、別のスコープに所有権を貸し与えたり出来るので、常に1つのスコープに所有されつつ、そのスコープが終了すればメモリの解放が行われる事を保証出来るらしい。

まだ自分でコード書いて試してみてないからアレだけど、説明だけ読んだ感じだとすごく良い仕組みのように感じる。

参考

Ownershipとかについては、このページで紹介されてて興味を持った。

こちらは所有権の移動(move)の説明。