一日一処: 全ては公式ドキュメントにあるがRustの事始めが最近で一番感動している件(初心者向け)
Rust
プログラミング言語全体で言えば、比較的若く、ここ数年で使われだした言語となる。ただし、いくつかの問題を抱えている。言語そのものは、非常に素晴らしいが市場がそれにおいつていない。たとえば、海外の「Why Rust is Stopping Your Success. Use C++ and C# Instead.」という記事が面白かった。言語そのものやそれに関わるエコシステムが十分に成熟したとは言えないため、流行りに乗っかって、導入するというのは、些か早計な判断ともいえる。
Rustのドキュメント
そんな、各界隈にて様々な論争が巻き起こっているわけだが、まだRustに触れたことない人は、ドキュメントを一度でもいいから読んでほしい。他の言語では、開発の準備などからはじめ、いきなり基礎の話など、開発に必要な情報を多く提示しているが、Rustのドキュメントでは、インストールが終了した次のセクションにて、数字を予想するゲームをもとに解説を行っている。
上記のRust公式のドキュメントから拝借したプログラムは、この様になっている。
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
ある程度のプログラム言語に慣れ親しんだ人にとってみれば、「数字予想ゲーム」の一言で、この全てを理解することができるだろう。このコードには、ロジックを作る際に最低限必要な考えが全て整っている。繰り返し、条件分岐、標準入出力、変数宣言など、読み手側が理解をしやすいようにこのテーマを選び、言語についての解説をしていることがわかる。最初にこれを見た時、Rustすごい。と感じた。やはり、公式ドキュメントは、知を得るために最適な手段だ。
数字予想ゲームで気になったコード
Rustでは、変数に代入した内容は不変となるため、修飾子が必要となる。それが下記のmut
部分だ。おそらく、Mutableを意味しているのだろう。他言語では可変であることを明示した記憶がないので、個人的にはなれないものだった。
let mut guess = String::new();
次に、expect
メソッドだ。多言語だと、特にテストフレームで目にすることが多いかもしれない。Rustのこの標準入力にメソッドチェーンされたメソッドは、何かを期待していそうなものに見えるが、与えている引数は、失敗した場合のメッセージだ。瞬間的にこのコードを見ると、「何かしらの期待をしている」メソッドに、「何かしら失敗した際の文言」を設定しているように見えるので、少々頭が混乱する。このメソッドについてのドキュメントを読めば、もちろん、渡された引数の文字列が、Panic時に出力されるというのはわかる。実際はMatchのほうが最適に思えるので、失敗時のメッセージそのものは渡さないような気もする。
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
変数宣言において、同名の変数名の宣言が許可されており、更に型を途中から変更することができるという魔法が備わっている。最初に標準入力の受け皿として準備したguess
変数だが、型の通常の推論では、文字列となるはずだ。ただその後に、下記のコードがあり、変数そのものの型を符号なし32ビットに置き換えている。これを見た時、変数の型を途中で変更するなんて、そんなのありなんだ。と驚愕した。確かに様々な情報を取り扱うなか、キャストによって型が変わり、変数を増やしとなった時に、この方が合理的なように思える。なぜなら、プログラムは一方向に処理していくため、その時点よりも前で使用しなくなった変数は不要になるため、同名でも、その後型が変わり、型に合わせた処理が準備されているのであれば、処理の流れで変数の型が明示的に変わることは些細なことなのかもしれない。
let guess: u32 = match guess.trim().parse()
最後にmatchだが、見た目通りで他言語のSwitch構文とほぼ同じだ。特にC#のメソッドへのパターンマッチングにも見えそうだ。この構文は、個人的に非常に面白く、前述の標準入力でも同じようなことが可能だ。それぞれのメソッドが単純に結果などだけを返却するのではなく、matchを用いれば、結果に合わせたそれぞれの処理を受け入れてくれる。自分でこのようなものを実装することは多くあると思うが、標準で備えているのは非常に嬉しい。特にGo言語と同様に例外ではなく、エラーに関するパターンマッチも可能となっているため、変に例外処理に悩まされることはないだろう。
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
全く本筋とは関係ないが、例外処理の有無もそれぞれの言語によって議論されているテーマのひとつだ。個人的には、Try-catch文なんか書きたくないし、特にJavaでの複数の例外をケアしなければならない、それが結局できないから、継承元のクラスで一つに集約し握りつぶす。そんな事があるくらいなら、RustやGoのようなエラー処理が簡潔で処理を書く側も簡単なのではないかなと思う。Rustのパターンマッチでこのようなことを考えていたが、非常に参考になり、面白い記事「「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件」があったので、紹介しておく。
ともあれ、Rustに関心のある方は、ぜひ公式のドキュメントを読んでみてほしい。他の章でも比較的わかりやすく説明されているように思える。
Discussion