はじめに
"ことわざをプログラミング"することで Rust を勉強していきます.
本日のお題: 時は金なり
西洋のことわざ「Time is money.」から。
時間は貴重なものであって、金銭と同じように大切で価値があるのだから、浪費するものではないという戒め。
時間は無駄に費やすものではなく、有効に使うべきである。
http://kotowaza-allguide.com/to/tokiwakanenari.html
本日の学び
- 標準入力
std::io
- 変数のキャスト
- 2つの文字列型:
&str
とString
- 時間に関するモジュール
std::time
の使い方 - 標準出力
println!
マクロ
GitHub レポジトリ
仕様
- 標準入力から時給を文字列入力
- 時給文字列から"秒給"を算出
- 一秒ごとに,「無駄に過ごした時間で稼げるはずだったお金」を標準出力
コード全体
// Proverb Programming: "Time is money"
// Count duration time, convert it to money, and display.
use std::time;
use std::io;
fn main() {
println!("Please input your hourly wage:");
let mut hourly_wage_string = String::new();
io::stdin().read_line(&mut hourly_wage_string)
.expect("Failed to read line");
hourly_wage_string = hourly_wage_string.trim_right().to_string();
println!("Your hourly wage is :{}",hourly_wage_string);
let hourly_wage: f32 = hourly_wage_string.parse().unwrap();
let second_wage: f32 = hourly_wage/60.0/60.0;
// Count time in second
let start_time = time::Instant::now();
let mut count_second:u64 = 0;
loop {
if count_second <= start_time.elapsed().as_secs() {
count_second += 1;
println!("You have wasted {:.0} secs. = {:.3} Yen",count_second,second_wage* (count_second as f32) );
}
}
}
実行結果
$./time_is_money
Please input your hourly wage:
907 # 時給を入力すると...
Your hourly wage is :907
# 起動から現在までに消費した時間 (duration) と,
# そこで得られるはずだったお金が一秒ごとに可視化されます
You have wasted 1 secs. = 0.252 Yen
You have wasted 2 secs. = 0.504 Yen
You have wasted 3 secs. = 0.756 Yen
You have wasted 4 secs. = 1.008 Yen
You have wasted 5 secs. = 1.260 Yen
You have wasted 6 secs. = 1.512 Yen
You have wasted 7 secs. = 1.764 Yen
You have wasted 8 secs. = 2.016 Yen
You have wasted 9 secs. = 2.268 Yen
You have wasted 10 secs. = 2.519 Yen
You have wasted 11 secs. = 2.771 Yen
...
コードの解説
1. 標準入力から時給を入力
まずは,時とお金を変換するための「時給」を入力させます.
Rust では標準入力の受け付けは以下のようにするらしいです.
let mut hourly_wage_string = String::new();
io::stdin().read_line(&mut hourly_wage_string)
.expect("Failed to read line");
参考: https://doc.rust-lang.org/book/guessing-game.html#processing-a-guess
io::stdin().read_line()
が本丸の標準入力処理を担います.
read_line()
の引数には,標準入力を受け取る String
型のオブジェクトが必要です.
ですので,予め次のように文字列を受け取るための String
インスタンスを生成しておきます.
let mut hourly_wage_string = String::new();
これは mutable でなくてはいけません.
こうして生成した空のhourly_wage_string
文字列インスタンスを,
io::stdin().read_line(&mut hourly_wage_string)
のように引数に渡します.
&mut
は「hourly_wage_string
の所有権を関数に借用させます,しかもその内容は変更可です」といった意味らしいです
(参考: https://doc.rust-lang.org/book/references-and-borrowing.html#mut-references).
※ 所有権・借用の概念はRustの根幹的概念ですが,まだまだ理解しきれていないところがあるので,後々勉強したいですね.
その次の行の
.expect("Failed to read line");
は例外処理で,read_line
関数が正しく処理されなかった時に引数のメッセージを出力して落ちるようになっています.
2. 時給文字列から"秒給"を算出
さて,受け取った時給は扱いにくいので,秒給に変換してやりたいです.
ただしhourly_wage_string
はString
型なので,これを計算可能な数値型にしてやる必要があります.
hourly_wage_string = hourly_wage_string.trim_right().to_string();
let hourly_wage: f32 = hourly_wage_string.parse().unwrap();
let second_wage: f32 = hourly_wage/60.0/60.0;
一番最後の行は単純に時給hourly_wage
を秒給second_wage
に変換しているだけです.
その手前二行で,入力された文字列hourly_wage_string
を浮動小数点hourly_wage
に型変換(キャスト)しています.
ここで一つ落とし穴が.
最初は標準入力から渡ってきたhourly_wage_string
を直接なんとかキャストしようと試みましたが,どうしてもエラーになりうまくいきませんでした.
ここでよくよく調べてみると,標準入力から渡ってきた
hourly_wage_string
には,数値の文字列だけでなく末尾に改行文字列\n
が含まれており,
これが型変換の邪魔をしていました.
なので型変換をする前に,String
クラスのtrim_right
メソッドで末尾の邪魔な文字列(空白,\n
など)を削除しています:
hourly_wage_string = hourly_wage_string.trim_right().to_string();
これはPythonで言うところのstr.strip
メソッドですね.
trim_right
メソッドの返り値はString
型とは似て非なる&str
型なので,
これをto_string
メソッドで再度String
型に戻してやります.
※ どうしてRustには2つの文字列型があるのか,まだご利益は感じられていないのが現状です...
こうして末尾の\n
が削除されて数値文字列だけになったはずのhourly_wage_string
文字列.これをString
型のparse
メソッドを使って浮動小数点であるf32
型にキャストします.
let hourly_wage: f32 = hourly_wage_string.parse().unwrap();
parse
メソッドの返り値は,キャストされた数値そのものではなく,Result
型と呼ばれる返り値用の特殊な型です.欲しい返り値本体はこのResult
型の内部に包まれている(wrap
)ので,解いてやる(unwrap
)してやることで得ることができます.
3. 一秒ごとに,「無駄に過ごした時間で稼げるはずだったお金」を標準出力
秒給を得られたら,一秒ごとに,そこまでの経過時間と,それを秒給で換算した金額を永遠に出力します.
ここで肝となるのは,時間を司るstd::time
モジュールを使いこなすことです.
use std::time;
let start_time = time::Instant::now();
let mut count_second:u64 = 0;
loop {
if count_second <= start_time.elapsed().as_secs() {
count_second += 1;
println!("You have wasted {:.0} secs. = {:.3} Yen",count_second,second_wage* (count_second as f32) );
}
}
まず,最初の時間を取得します.
let start_time = time::Instant::now();
start_time
はtime::Instant
型と呼ばれる構造体のインスタンスです.
直感的には,あるシステム時間のスナップショット的なオブジェクトです.
time::Instant
型の構造体にはelapsed
メソッドというものが実装されており,
これを呼び出すとtime::Instant
が作成された時間からの経過時間(duration)が返ってきます.
let duration: Duration = start_time.elapsed()
経過時間はDuration
型と呼ばれる時間を表す構造体で表現されており,as_secs
メソッドを呼び出すことで経過時間が秒で数値として得られます.
let duration_sec: u64 = duration.as_secs()
この処理を無限ループloop{...}
の内部で常に回しておき,
一秒経過するごとに経過時間と累積金額を出力しています.
println!("You have wasted {:.0} secs. = {:.3} Yen",count_second,second_wage* (count_second as f32) )
標準出力を担うprintln!
マクロへは,第一引数にフォーマットを,第二引数以降にフォーマットの中の{}
に当たる箇所に代入したい変数を渡します.
{}
の内部に{:.3}
などと書くと,小数点下三桁までにそろえて出力してくれたりします.
最後に
「この書き方は普通じゃない」「こんなエレガントな書き方があるよ」というアドバイスがありましたら,ぜひコメントや編集リクエストをお願いします.
次のことわざ: 「蛙の子は蛙」でクラスの作り方を勉強