2016年2月27日土曜日

C++プログラマー向けRust 翻訳シリーズ1

導入 - hello world!


もし今現在、あなたがCやC++を使用しているとしたら、その理由はおそらく他に選択肢がないからだろう。(低レベルレイヤーへのアクセスが必要だとか、パフォーマンスを極限まで追求しなければならないとか、あるいはその両方かも)
RustはCやC++と同レベルのメモリ抽象性(翻訳者注:生ポインタへのアクセスや不安全なキャストなどを指していると思われる)、パフォーマンスを発揮しつつも、より安全で生産性を向上させることを目的にしている。

厳密に言えば、世の中にはC++よりも使いやすいプログラミング言語が溢れている。Java, Scala, Haskell, Pythonなどなど。だが、抽象性が高すぎ(メモリアドレスが取れない、ガベージコレクションを使わざるを得ないなど)たり、パフォーマンス(パフォーマンスの予測が立たないか、単純に遅い)の問題で使えないのだろう。Rustではガベージコレクションを強制されることはないし、C++同様、生ポインタをいじることもできる。RustはC++の「自分が使用した分だけ払え」精神に倣っている。機能を使用しなければ、その機能の存在自体にパフォーマンスを犠牲にする必要はないのだ。さらに、Rustの言語仕様はすべて、予測可能なコスト(そして、大抵は極小)内にある。

これらの制約によりRustはC++の類稀で有望な代替言語となっているけれども、Rust自体にも利点はある。
一つ目はメモリ安全性だ。Rustの型システムにより、C++で一般に見られるようなメモリ関連のエラーは発生しない。
メモリリーク、未初期化領域アクセス、nullポインタ。すべてRustでは起こりえない。
その上、Rustでは他の制約の範囲内ならば、安全性問題を回避する手段も用意している。例えば、配列は境界値チェックが行われる。(無論、そのコストが惜しければ、(安全性を犠牲にして)回避することもできる。Rustではunsafeブロックを使って、その他様々な非安全な事柄とともに行うことができる。根底的に、Rustでは非安全なことはunsafeブロック内にとどまりプログラム全体に影響を及ぼすことはないのだ)
最後に、Rustは現代のプログラミング言語の様々なコンセプトを取り込み、システム言語の世界に導入している。願わくば、それによってRustが生産的かつ効率的で、遊び心のあるものになっておらんことを。

これから、Rustをダウンロード、インストールし、最低限のCargoプロジェクトの生成、そしてHello worldの実装を行う。


Rustの入手


Rustはhttp://www.rust-lang.org/install.htmlから入手できる。ダウンロードしたファイルにはコンパイラ、標準ライブラリ、そしてCargoと呼ばれるRustのパッケージマネージャ兼ビルドサポートツールが含まれている。

Rustは3段構えでリリースされており、それぞれ安定版、β版、ナイトリー版となっている。Rustは短期リリーススケジュールを採用しており、6週間ごとにリリースが更新される。
更新日にナイトリー版がβ版に、β版が安定版になるのだ。

ナイトリー版は毎晩更新されるため、最新の機能を試したり、長期的に動作するライブラリの製作者向けだ。

一方、安定版は大抵のユーザに最適である。Rustの安定性は安定版のみで保障されている。

β版は、ユーザのコードが想定通り動作していることをチェックイン(翻訳者注:CIをCheck inのabbreviationと解釈した。間違ってるかも)の段階で確認する目的で作られている。

以上より、安定版を入手しよう。LinuxやOS Xユーザならば、以下の方法で簡単に入手できる。
curl -sSf https://static.rust-lang.org/rustup.sh | sh
他のインストール方法は、http://www.rust-lang.org/install.htmlを参照されたし。

ソースコードはhttps://github.com/rust-lang/rustで入手できる。コンパイラをビルドするには./configure && make rustcコマンドを入力しよう。もっと詳しい手順については、building-from-sourceを参照されたし。


Hello World!


Rustのプログラムをビルドする最も簡単で一般的な方法は、Cargoを使用することだ。 helloという名のプロジェクトを作成するのに、cargo new --bin helloと入力しよう。これで、helloという名のディレクトリができ、Cargo.tomlというファイルとmain.rsというファイルが入ったsrcという名のディレクトリが入っているはずだ。
Cargo.tomlにプロジェクトに関するメタデータや依存するライブラリなどが定義されている。詳しくは後述する。
ソースコードはすべてsrcディレクトリに置く。main.rsにはすでにHello Worldプログラムが入っている。以下のような感じだ。
fn main() {
    println!("Hello world!");
}

このソースをビルドするにはcargo buildと入力する。ビルドして実行するには、cargo runと入力する。後者の方法をとったなら、コンソールに挨拶が表示されるはずだ。やったね!
Cargoがtargetディレクトリを生成し、その中に実行ファイルが配置される。
もし、コンパイラを直接起動する必要があるなら、rustc src/hello.rsコマンドを使用しよう。helloという名の実行ファイルが作成される。他のオプションについては、rustc --helpコマンドを参照しよう。

さて、コードに戻ろう。数点気になる箇所がある。関数やメソッドを定義するのにfnキーワードを使っているのだ。main()がプログラムの規定エントリーポイントだ(プログラムの引数については後ほど見ることにしよう)。
C++のように、個別の定義やヘッダーファイルは存在しない。println!はRust流のprintfだ。!記号がマクロであることを意味している。マクロについてはとりあえず、普通の関数のようなものと思っておけばいいだろう。
importやincludeすることなく、標準ライブラリの一部が使用できる(後ほど詳しく語る)。println!マクロはその一部分に含まれているのだ。

例を少し変えてみよう。

fn main() {
    let world = "world";
    println!("Hello {}!", world);
}
letで変数を宣言する。worldは変数名で型はstringだ(技術的には&'static strだが、また別立てで詳しく解説しよう)。型を明示する必要はない。型は推論されるのだ。

println!文で{}を使用するのは、printf関数内で%sを用いるようなものだ。実際、{}は%sよりも少々一般的で、もし変数の型がstringでなければ、コンパイラがstring型への変換を試みるのだ。この類の事柄は、容易に遊んでみることができる。いろんな文字列、数字で試してみよう(整数リテラルとfloatリテラルなら動作する)。

好みに応じて、world変数の型を明示することもできる。
let world: &'static str = "world";
C++ではT型の変数xを定義する際、T xと記述した。Rustでは、let文内だろうが、関数宣言だろうが、x: Tと記述する。ほとんどの場合、let文では型の明示は行う必要がないが、関数の引数では明示しなければならない。新たな関数を追加して実際の動作を確認してみよう。

fn foo(_x: &'static str) -> &'static str {
    "world"
}

fn main() {
    println!("Hello {}", foo("bar"));
}
関数fooは文字列リテラル引数_xを一つ持っている(main関数内で"bar"を渡している)。

関数の戻り値の型は->の後に記述する。関数の戻り値がない場合(C++のvoid関数)は、戻り値を明示する必要はない(main関数でもそうしていた)。あなたが冗長性を好む人間ならば、-> ()と書くこともできる。()はRust流のvoid型だ。

Rustでは、returnキーワードは必要ない。関数本体の最後の式(あるいは詳細は後述するが、他のどんな包含式)がセミコロンで閉じていなければ、戻り値になる。したがって、foo関数は常に"world"文字列を返す。なお、returnキーワードは存在しており、短絡評価のために用いることができる。"world"return "world";と書いても同じ意味になるのだ。

理由


前述の機能の動機付けを行おう。
ローカル変数の型推論は便利であり、パフォーマンスや安全性を犠牲にすることなく使用できる(現バージョンのC++にも入っている)。
より目立たない利便性として、言語仕様がキーワード(fnletなど)と紐づくことが挙げられる。そのため、ツールや目で識別するのが簡単になる。一般的に言って、Rustの見た目はC++のものよりも単純で一貫性がある。
println!マクロはprintf関数よりも安全だ。つまり、引数の数は静的に文字列内の「穴」の数と突合され、引数自体も型チェックが行われる。これは要するに、printf関数でありがちな、型違いのメモリ領域を表示したり、誤ってメモリスタックをより深くまで掘り下げてしまうなどのミスを犯さなくなるということだ。
これらはほんの些細なことだが、Rustの仕様の背景にある哲学を描き出していると嬉しい。


  1. こちらは、Display traitを使用してプログラマーが記法をカスタマイズしたバージョンだ。このDisplay traitはJavaのtoStringのような役割を果たす。{:?}という記法もでき、こちらはデバッグの際に利便性を発揮するコンパイラ生成の文字列を表示する。printf関数と同じように、その他にも様々なオプションがある。
  2. foo関数の引数は使用していない。普通なら、コンパイラがこのことについて警告を出してくれるが、変数名に_という接頭辞をつけることでこの警告を回避している上、変数名を付す必要はなく、ただ単に_という名前をつければいいのだ。



翻訳者後記:以前、C++とRustの概念比較の記事を書いてから1年以上が経ち、Rustのバージョンも1.5とだいぶこなれてきました。
暇があるときにC++習熟者向けに書かれたRust解説書のgit bookバージョンであるhttps://github.com/nrc/r4cpppを軽く訳していこうと思います。
のんびりお付き合いいただければと思います。
なお、本文は意訳だらけなので、原語版と比較して読むのはあまりお勧めしません。

原文: https://github.com/nrc/r4cppp/blob/master/hello%20world.md

0 件のコメント:

コメントを投稿

なにか意見や感想、質問などがあれば、ご自由にお書きください。