今日の内容
- Rustで標準入力をしよう
- 改行なしの標準出力
- use
- HashSet
はじめに
少し前に標準出力を学びましたが、まだ標準入力を学んでいませんでした。しかし、Rustでの標準入力は少しめんどくさいので、覚悟をしておいてください。
Rustでの標準入力
以下は入力した文字列を文字列aに保存し、それを表示するプログラムです。
use std::io;
fn main(){
println!("↓入力");
let mut a = String::new();
io::stdin().read_line(&mut a).expect("Failed to read!");
println!("You typed: {}", a);
}
/******** 実行結果 ********
↓入力
123
You typed: 123
*************************/
ちなみに、C言語では以下のようになります。
#include <stdio.h>
int main(){
char a[256];
printf("入力: ");
scanf("%s", a);
printf("You typed: %s\n", a);
return 0;
}
入力を求める文が明らかに違いますね。Rustを学ぶ気力が失せるかもしれませんが、実はそれぞれについて意味を理解すれば、「なるほど」と、「だからRustはすごいのか」となります。過言です。
それでは、ちゃちゃっと入力できるようになりましょう。
まず、今まではなかった「use std::io;」が使われています。標準入力をするには、「stdin()」が必要ですが、これは「io」ライブラリが必要です。それは「std」という標準ライブラリに含まれています。これは、「std::io::stdin()
」のようにすれば使えますが、毎回「stdin()」のためにこれを書くのはめんどくさいですし、可読性も悪くなります。ここで、「use」を使うことで以降省略して書くことができます。
次に、stdin().read_line()とありますが、これは1行読み込みを表します。()内には、入力先を指定します。今回は、先にString型のaを用意したので、aを指定しています。「&mut a」となっていますが、「mut」は入力のため可変でないといけません。また、「&」は、参照を表します。文字列の所有権うんぬんかんぬんエトセトラっと、まあ、とりあえずread_line()内には「&mut 名前」と入れることだけ覚えてください。
次の.expect()は、前述したread_line()についてきたものです。read_line()はコンソールから一行を読み取りますが、もし読み取りに失敗した場合、「Err」を返します。成功すれば、「Ok」を返します。この.expect()は、Errがかえって来た場合に指定した文字列を表示する処理です。Errが出れば標準でプログラムがパニックしますが、そこに指定した文字列が表示されます。
Rustではプログラムのバグを徹底的に排除するため、さまざまな可能性を考慮し、それに対応したプログラムを書かないとコンパイラに怒られてしまいます。
改行なしの出力print!
以前にRustでは標準出力に、println!とprint!の二つのマクロが用意されていると紹介しました。
しかし、ここまでprint!を使ったことはありませんでした。いままでprintln!ばかり使ってきたのには理由があります。実は、print!だけではすぐに出力することができないのです。(入力の後に表示されたりする)そこで、std::io::stdout().flush()
を使用してすぐに出力させます。flush()は、std::io::Write
内にあります。
use std::io;
use std::io::Write;
fn main(){
let mut a = String::new();
print!("入力: ");
io::stdout().flush().unwrap();
io::stdin().read_line(&mut a).expect("Failed to read");
println!("You typed: {}", a);
}
/******** 実行結果 ********
入力: 314
You typed: 314
*************************/
さて、print!マクロが使えるようになりました。ここで、flush()を使うとまだ表示されいない文字をすぐに表示できるようになりますが、そもそも表示されていない文字がない場合、flush()はいったい何を表示すればよいでしょうか。これも表示できればOk、表示するものがない場合Noneを返します。unwrap()は、Okであれば文字列を返し、Noneであればパニックします。
演習問題
stdinを覚えるために、いくつかの問題を用意したので解いてみましょう。
問題1 入力された値を3倍にして表示するプログラムを作成しよう。
use std::io::*;
fn main(){
let mut a = String::new();
print!("input number: ");
stdout().flush().unwrap();
stdin().read_line(&mut a).expect("Failed to read line!");
let a:i32 = a.trim().parse().expect("Please type a number!");
println!("{} × 3 = {}", a, a*3);
}
/******** 実行結果 ********
input number: 4
4 × 3 = 12
*************************/
use std::io::*;
は、「*(アスタリスク)」がついていますがこれは、io内の 全て を使えるようにします。よって、stdout()やstdin()はもちろん、flush()も、Writeをわざわざ指定せず使えるようになります。ただし、これは使わない物もインポートするため、名前の衝突が意図せずおこる場合に注意です。
また、use std::io;
は、ioの中身ではなく、std内のio自体をインポートするため、「io::stdin()」のように記述する必要があったことを忘れないでください。
次に、let a:i32 の部分ですが、すでにaという名前の変数は存在しているのに、なぜこれが動くのでしょうか。Rustでは 再宣言 が許されています。これは、元ある変数を、以降別の型にして取り扱いたい場合に便利です。なぜなら、変数が増えることなく、さらに名前はわかりやすいままで中身だけ変わるので、とても簡素なプログラムになり可読性がとても向上します。今回は文字列ではなく、整数型として扱いたいので、i32型として再宣言しています。右辺のa.trim().parse().expect()
についても解説します。
trim()
とは、文字列の先頭と末尾の空白を取り除き、改行なども取り除いてくれます。
parse()
は、文字列を特定の型に変更します。変更できない場合はErrが出るので、expect()を用いてその場合の処理をしています。今回は「let a:i32 = 」としたので、String型からi32型にしてくれますが、parse::<i32>()
としても同じように変更できます。
問題2 入力した数値が素数のかぎり繰り返し、素数でなければ、素数でない例を示し、言えた素数の数を表示して終わるゲームを作成しよう。また、素数でも同じ数字だったらもう一度入力させよう!
use std::collections::HashSet;
use std::io::*;
fn main() {
let mut list = HashSet::new();
loop {
let mut is_string = String::new();
print!("Input number: ");
stdout().flush().unwrap();
stdin().read_line(&mut is_string).expect("Failed to read!");
let is_string: u32 = is_string.trim().parse().expect("Please type a number!");
for i in 2..is_string {
if is_string % i == 0 {
println!("{} IS NOT A PRIME NUMBER!", is_string);
println!("{} = {} × {}", is_string, i, is_string/i);
println!("{} prime numbers imputed!", list.len());
return;
}
}
if list.contains(&is_string) {
println!("The number already imputed!");
continue;
} else if is_string == 0 || is_string == 1 {
println!("{} IS NOT A PRIME NUMBER!", is_string);
println!("{} prime numbers imputed!", list.len());
return;
} else {
println!("Nice!");
list.insert(is_string);
}
}
}
/******** 実行結果 ********
Input number: 2
Nice!
Input number: 3
Nice!
Input number: 5
Nice!
Input number: 7
Nice!
Input number: 9
9 IS NOT A PRIME NUMBER!
9 = 3 × 3
4 prime numbers imputed!
*************************/
だいたいは説明せずとも理解できると思うので、これを自力で理解できることが最終目標とします。しかし、HashSetについては初めて出てくるので説明します。まず、この問題では、入力された素数が新しい素数か、既存の素数かを判断する必要があります。ここで、データを配列の形で保存するわけですが、同時に入力できた素数の数を表示するため、「.len()」をつかって長さを取得したいので、可変長のデータにする必要があります。(もちろんカウント専用の変数を作ってもよいですが、どうせなら可変長にしたほうがすっきりしませんか?)そこで、RustにはCollectionsモジュールが用意されています。その中の「HashSet」は、特徴として「可変長」で、「同じデータは使えない」かつ、「特定のデータが入っているかどうかを判定できる」です。今回のプログラムにとても最適なコレクションですね。似たものに「HashMap」がありますが、少し異なるものです。今回は解説しません。
HashSetは HashSet::new()で新しいHashSetを作成し、名前.contains(&変数名)でtrue/falseとして取得できます。
追加は、名前.insert(変数名)とすることで変数の値を追加できます。
おわりに
おつかれさまでした。今回は標準入力について学びました。次回は所有権について学んでいきます。
ご精読ありがとうございました。