コース目次 / 第3章
成果物 — 買い物メモ
打ち込んだものをためて合計を出すCLIを、Result のエラー処理つきで作ります。
いよいよ道具を作ります。打ち込んだものをためて、合計を出す買い物メモです。
今日のゴール
> りんご 180
りんご を入れました。
> パン 250
パン を入れました。
> list
1. りんご 180円
2. パン 250円
合計 430円
> quit
またどうぞ。1. ミニ解説: 今日使う4つ
| 書き方 | 何をするか |
|---|---|
struct Mono { ... } |
形に名前を付ける。「名前と値段を持つもの」を1つの型にする |
Vec<Mono> |
並べてためる入れ物。あとから増やせる |
match 値 { ... } |
場合分け。全部の場合を書くまで通らない |
.parse::<u32>() |
文字を数に変える。うまくいったかどうかごと返ってくる(これが Result) |
Result がこの章の主役です。「文字を数に変える」は失敗しうる処理です。多くの言語では、失敗したときに例外が飛ぶか、こっそり0が返ります。Rustは「成功(Ok)か失敗(Err)か」を値そのものに含めて返すので、受け取る側は両方を書かないとコンパイルが通りません。
試食章の match の網羅チェックが、ここで効いてきます。
2. 書く
src/main.rs を、まるごとこう書き換えます。少し長いですが、上から順に読めるはずです。
use std::io::{self, Write};
struct Mono {
namae: String,
nedan: u32,
}
fn main() {
let mut memo: Vec<Mono> = Vec::new();
println!("買い物メモ");
println!("「名前 値段」で追加、list で一覧、quit で終わり。");
loop {
print!("> ");
io::stdout().flush().unwrap();
let mut gyou = String::new();
if io::stdin().read_line(&mut gyou).unwrap() == 0 {
break;
}
match gyou.trim() {
"" => continue,
"quit" => break,
"list" => ichiran(&memo),
hoka => tsuika(&mut memo, hoka),
}
}
println!("またどうぞ。");
}
fn tsuika(memo: &mut Vec<Mono>, gyou: &str) {
let bubun: Vec<&str> = gyou.split_whitespace().collect();
if bubun.len() != 2 {
println!("「名前 値段」のように、2つに分けて書いてください。");
return;
}
// parse は「うまくいったかどうか」ごと返してくる。両方の場合を書かないと通らない。
match bubun[1].parse::<u32>() {
Ok(nedan) => {
memo.push(Mono {
namae: bubun[0].to_string(),
nedan,
});
println!("{} を入れました。", bubun[0]);
}
Err(_) => {
println!("値段は数字で書いてください。「{}」は数字に見えません。", bubun[1]);
}
}
}
fn ichiran(memo: &[Mono]) {
if memo.is_empty() {
println!("まだ何も入っていません。");
return;
}
let mut goukei = 0;
for (i, mono) in memo.iter().enumerate() {
println!("{}. {} {}円", i + 1, mono.namae, mono.nedan);
goukei += mono.nedan;
}
println!("合計 {}円", goukei);
}読みどころが3つあります
(1) &mut と &
tsuika は &mut Vec<Mono>(書き換えるために借りる)、ichiran は &[Mono](見るだけ借りる)を受け取っています。試食章でやった「借りる」が、ここで実用になっています。書き換える側だけ mut を付けるので、読んだ人は「この関数は中身を変える」と一目で分かります。
(2) match gyou.trim() の hoka
""・"quit"・"list" 以外の全部を hoka という名前で受け取っています。match は「それ以外」を書かないと通りません。書き忘れられない仕組みです。
(3) Ok と Err
parse の結果を match しています。成功した中身を nedan として取り出せるのは Ok の枝の中だけです。失敗しているかもしれない値を、うっかりそのまま使うことができません。
3. 動かす
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
いくつか打ってみてください。
> りんご 180
> パン 250
> list
> quitこわしてみる
2つ、わざと変なことをしてください。どちらも落ちません。
(1) 値段に文字を打つ
> コーヒー あああ値段は数字で書いてください。「あああ」は数字に見えません。プログラムは止まりません。 Err の枝がそれを引き受けています。
(2) 名前だけ打つ
> りんご「名前 値段」のように、2つに分けて書いてください。「落ちない」は、書いた人が偉いのではありません。Rustが「失敗しうる場所では、失敗の場合も書け」と強制した結果です。この強制が、Rustが「安全」と言われる理由の半分です(もう半分が所有権です)。
ハマったら
「mismatched types」と出る
型が合っていません。エラーの中に expected と found が出ているので、その2つを見比べてください。u32 と i32、String と &str の取り違えがほとんどです。
「cannot borrow as mutable」と出る
tsuika(&mut memo, hoka) の &mut が抜けているか、let mut memo の mut が抜けています。エラーの help: に、どちらを直すか書いてあります。
「non-exhaustive patterns」と出る
match の枝が足りません。hoka => ... の行(それ以外の場合)を書き忘れていないか確かめてください。
ZIPで戻したい
このページの下のZIPは、この章を終えた状態のフォルダです。解凍して、その中で作業を続けられます。自分で書き換えた部分は消えてしまうので、先に自分のファイルを別の場所へ待避してから使ってください。
こうなっていればOK
うまくいかないときは、この章の完成ファイル一式(ZIP)をダウンロードして続きから進めても大丈夫です。
卒業まであと1章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。