Rustで実際にプログラミングをしてみました。
いままで、同じプログラムをPython、GO言語、C言語 で作ってみていますが、Rustの場合は、いきなりhello,worldの次のプログラムとしてはハードルが高かったです。
もっとも、開発者に好かれる理由も納得しました。
Stackoverflowの調査では、2016-2019と4年連続でもっとも好きなプログラミング言語に選ばれているにもかかわらず、実利用では2019年時点で25位内にもはいってこない理由もおぼろげながら見えました。
Rustでプログラミング
他言語(Python, Go, C)との比較
同じ内容のプログラムを書いても、プログラミング言語が違うだけでいろいろ差がでています。
コーディング量
Python(48行) < Rust(80行) < Go言語(85行) < C言語(100行)
作成にかかったおおよその時間(調査時間含む)
C言語(0.3H:参考値) < Go言語(1.5H) < Python(2H) < Rust(6H)
過去の経験などにも依存するのでこれだけでは語れませんが、Rustだけ段違いに学習コストがかかりました。
C言語は、既知の言語だったため、さすがに早いです。
Pythonの方がコード量が少ないのにGo言語より時間がかかったのは、ライブラリの調査に時間がかかったのと、行末にセミコロンがないとか、インデントでブロックが決まるとか、C/C++やJavaに慣れていると、その違い部分に多少のとまどいがあったからです。
通常のプログラミング言語であれば、他の言語に精通してさえすれば、調べながらでも数時間で書けるはずのプログラムが、Rustではその3倍。
書き方は、よく知っているC/C++に似ているといわれているにもかかわらずです。
一般的にも難しいといわれているのは、その通りと実感しました。
利用者が広まらない一つの理由は、このハードルの高さに一因がありそうです。
実行モジュールサイズ(Windows 64bit)
C言語(58Kbyte) < Rust(184Kbyte) < Go言語(2.29Mbyte)
リリースモードでビルドした結果です。
Go言語だけ飛びぬけて巨大になっています。
おそらく、初期モジュールが大きいのでしょうから、書いたプログラムが大きくなったからといってこのままモジュールサイズまで大きくなるとは限りませんが、これだけ小さなプログラムのモジュールが2Mを超えるのは、組込み系などでは問題になるケースもあるかも知れません。
Rustは、C言語ほどではありませんが、十分小さくなっています。
Rustでプログラミングつまった点
他の言語と比較しとまどったのは、例えばGo言語の場合、Javaならこう書くGoだとどう記述するの?
C/C++だとこういう関数があるけど、Goだと、どういう関数があって、その使い方は?
というのを調べるだけで済みました。並列処理等の特徴的なところまで踏み込んでいないからか、特に新たな概念もでてこなかったので、他のメジャーな言語を1つ習得していれば習得は比較的容易です。
一方、Rustは、C/C++に似ているとはいえ、ライフタイムと所有権(参照、束縛)、エラー処理方法等大きく異なっていて、そこの学習コストがかかります。
逆にこのあたりさえクリアしてしまえば、他の言語と特に変わるものではなく作りたいものを作ることができます。
Rustでとまどったポイント
- ライフタイムと所有権への対処
- エラー処理を列挙型で実施するResult、Optionとmatchや?での処理方法
- 型づけが厳しいのでその変換方法
- エラー処理未実装の場合の対処方法,expect,warap
オブジェクト指向は試していないので、ゼロコスト抽象化部分ついては、今回はあげていません。
ただ、調べた感じではライフタイムや所有権への対処と比べれば、C++等でしっかりオブジェクト指向をやっていれば、理解しやすい感触です。
抽象化についていえば、オブジェクト指向そのものの理解の方が難しそうです。
Rustでプログラミング、Rustが愛されている理由
Rustを利用しているプログラマは、本来あるべき姿をコンパイル時にきちんとチェックして問題がないようにしてくれるという、将来に期待させる技術に魅了されているのだと思います。
たしかに最初は難しいと感じるのかもしれませんが、実際に使いだせば、他言語とそう違いがあるとも思いません。
もっともプログラマという人種は、厳しいRustに、いじめられるのが好きなのかも知れないですが(笑)
Rustが愛されている理由
- ライフタイムと所有権をコンパイルでチェックしてくれる(メモリ安全性)
- エラーメッセージが親切
- 出来上がりモジュールがコンパクト(ネイティブ)で単独で動作する
- 抽象化がコンパイル時に解決できる(ゼロコスト抽象化で高速)
- C/C++に近いものの、型推論など最近の技術も実装
ライフタイムと所有権をコンパイルでチェックしてくれる(メモリ安全性)
C/C++等で開発していると、ポインタを宣言しても中身の確保ができていない状態で利用したりとか、ポインタの指し示す領域を変えてしまっていて、動くときと動かない時があるとか、ときどき経験します。
ある環境では動作していたのに、他の環境に移植したり、コンパイラのバージョンアップに伴いなぜか、コアをはいて動かなくなるなどというのも何度も経験しました。
本来は、メモリ管理がしっかりしていれば発生しないことが、適当だからたまたま動いた、なんてことがあるわけです。
それが、Rustではコンパイラがチェックしてくれる。
これは本来あるべきことで、このあたりで悩んでいた開発者としては期待してしまうわけです。
コンパイラが十分チェックしているからか、エラーメッセージが驚くほど親切です。
どこの部分のどのような理由でコンパイルエラーにしているかが、はっきりと示されます。
これは、嬉しいですが、逆に言えばわかって書いているのにエラーにされているという部分もあるわけで、他の言語からくるといらいらする部分でもあります。
ネット上でもこの部分はあまり見ないので、実際に使っていない人が記事にしているのも少しはあるとは思いますが、それ以上に慣れるとあまりエラーを出さずにすむのかも知れないです。
出来上がりモジュールがコンパクト(ネイティブ)で単独で動作する
単独動作でかつモジュールもコンパクトなのは、実際嬉しいです。
モジュールが大きいとよほどいい環境でないと起動がもっさりして、やはりストレスになります。
抽象化がコンパイル時に解決できる(ゼロコスト抽象化で高速)
ジェネリック、マクロ、アノテーション等を使えば今まででもある程度できていますが、継承・ポリモフィズムで書いたときにこの実装になるのは、ありがたいことです。
もっとも、コールバック関数や機能の追加など、後で拡張したりで動的にしか決められないものについては、それを考慮した実装をする必要もありそうですが、割合的には少ないし実装できるため問題ないようです。
C/C++に近いものの、型推論など最近の技術も実装
純粋にありがたいです。
Rustによる、タイピング練習用プログラム
実際のRustのソースコードを参考になるかはわかりませんが、のせておきます。
※インストールはじめて半日で作成したものなので、手探りで書いており実際には、より簡単に書けそうですが、作り方よりもプログラミングはやってみることの方がよほど短時間で作れるし勉強になるという例として参考にしてください。
タイピング練習プログラムの概要
- ファイルから複数の文字列を読込み、一行をランダムに表示
- ユーザから一行入力
- 表示・入力文字列の一致率と入力時間を表示
- 入力なしの場合終了、あれば表示から繰返し
実際の動作結果
タイピング練習プログラム(Rust版) *** 表示された文字を入力してください asfdsa > asd 正解率: 33 パーセント 時間 2.17 秒 表示された文字を入力してください asdf jkl; > asdf jkl; 正解率: 100 パーセント 時間 3.37 秒 表示された文字を入力してください asfdsa > おつかれさまでした!
ソースコード
// // タイピング練習プログラム(Rust版) // extern crate rand; use std::io; use rand::Rng; use std::time::{Instant, Duration}; use std::io::Write; use std::process::exit; use std::fs::read_to_string; // タイピング練習プログラム(main) fn main() { println!("*** タイピング練習プログラム(Rust版) ***"); let filename = "./touchtype.txt"; let lines:Vec = match read_file(filename) { Ok(v) => v, Err(e) => { println!("{} -- ファイル:{}", e, filename); exit(1); }, }; loop { println!("\n表示された文字を入力してください"); let nn = rand::thread_rng().gen_range(0, lines.len()-1); let prtstr:String = lines[nn].to_string(); println!(" {}", prtstr); print!("> "); io::stdout().flush().unwrap(); let mut inpstr = String::new(); let s_tm = Instant::now(); io::stdin().read_line(&mut inpstr).unwrap(); let elsp:Duration = s_tm.elapsed(); inpstr = inpstr.trim_end().to_string(); if inpstr.len() == 0 { // 未入力の時 println!("おつかれさまでした!"); break; } let urate = calc_hit_rate(&inpstr, &prtstr); println!(" 正解率: {} パーセント 時間 {}.{:02} 秒", urate, elsp.as_secs(), elsp.subsec_millis()/10); } } fn read_file(filename: &str) -> Result<Vec<String>, io::Error> { let mut lines:Vec = Vec::new(); let fdata = read_to_string(filename)?; let v : Vec<&str> = fdata.trim().split("\n").collect(); for s in v { let st:String = s.trim_end().to_string(); if st.len() > 0 { // 空行でない場合には格納 lines.push(st); } } Ok(lines) } // 表示文字列と入力文字列の一致度合いを返す fn calc_hit_rate(inpstr:&str, prtstr:&str) -> u32 { let mut urate: u32 = 0; let mut cprt = prtstr.chars(); for ci in inpstr.chars() { let cp = match cprt.next() { Option::None => break, Option::Some(val) => val, }; if ci == cp { urate = urate + 1; } } let frate = urate as f32 * 100.0 / prtstr.to_string().len() as f32; frate as u32 }
以上、今回は、Rustでタイピング練習プログラムをつくってみました。
タイピング練習プログラムはさまざまな言語仕様を学ぶのに適した題材です。
何か新しいプログラミング言語を学ぶときには、是非参考につくってみてください。
-
Hello,worldの次のC言語のプログラム【Python、Go言語との比較も実施】
同じプログラムを複数のプログラミング言語で記述してみました。 やってみると、その違いがよくわかります。 タイピング練習の簡単なプ ...
続きを見る
-
プログラムの勉強でHelloWorldの次には何をつくったら良いか?【Go言語版】
新しいプログラミング言語の勉強をはじめたのだけど、Hello,World!の次って何をつくればいいか悩む そんな時 ...
続きを見る
-
HelloWorldの次に作るべきプログラム【つくるとわかるPythonの特徴】
先日Go言語版をだしたので、Pythonはじめたときにどんなものをつくったのか気になって見てみたら、プログラミング言語が異なると ...
続きを見る
最後までお読みいただき、ありがとうございました。