chapter1에서 생성했던 디렉토리인 projects로 이동하고 아래 예제를 입력해 새로운 프로젝트를 생성합니다.
$ cargo new guessing_game --bin
$ cd guessing_game
chapter1에서처럼 "Hello,world!" 프로그램이 생성됩니다.
cargo run 입력 시 Hello, world! 가 출력되는 것을 볼 수 있습니다.
src/main.rs에 아래의 코드를 작성합니다. 사용자가 추리한 값을 입력받아 그대로 출력하는 코드입니다.
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
io
: 사용자의 입력을 받고 결과값을 표시하기 위한 input/output 라이브러리fn
: 새로운 함수를 선언println!
: string을 화면에 표시하는 매크로아래의 코드로 사용자의 입력값을 저장할 공간을 생성할 수 있습니다.
let mut guess = String::new();
변수를 생성하는 let
문을 사용합니다.
러스트에서 변수는 기본적으로 불변입니다. 변수 앞에 mut
을 사용하면 가변변수로 만들 수 있습니다.
let foo = 5; // immutable
let mut bar = 5; // mutable
String::new
에 있는 ::
은 new
가 String
타입의 연관함수임을 나타냅니다. 연관함수는 하나의 타입을 위한 함수이며, 이 경우에는 하나의 String
인스턴스가 아니라 String
타입을 위한 함수입니다. 몇몇 언어에서는 이를 정적 메소드라고 부릅니다.
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
위의 코드는 사용자에게 입력을 받는 코드입니다.
만약 프로그램 시작점에 use std::io
가 없다면 함수 호출 시 std::io::stdin
처럼 작성해야합니다. 사용자로부터 입력을 받기 위해 표준 입력 핸들에서 .read_line(&mut guess)
메소드를 호출합니다. 또한 read_line
에 &mut guess
를 인자로 하나 넘깁니다. read_line
은 인자로 넘긴 문자열에 사용자 입력을 저장할 뿐만 아니라 하나의 값을 돌려줍니다. 여기서 돌려준 값은 io::Result
입니다.
Result
타입은 열거형(enumerations)로써 enums라고 부르기도 합니다. 열거형은 정해진 값들만 가질 수 있으며 이러한값들은 열거형의 variants라고 부릅니다. Result
의 variants는 Ok
와 Err
입니다. Ok
는 처리가 성공했음을 나타내며 내부적으로 성공적으로 생성된 결과를 가지고 있습니다. Err
는 처리가 실패했음을 나타내고 그 이유에 대한 정보를 가지고 있습니다.
이러한 Result
는 에러처리를 위한 정보를 표현하기 위해 사용되며 다른 타입들처럼 메소드들을 가지고 있습니다. io::Result
인스턴스는 expect
메소드를 가지고 있습니다. 만약 io::Result
인스턴스가 Err
일 경우 expect
메소드는 프로그램 작동을 멈추게 하고 인자로 넘겼던 메세지를 출력하도록 합니다. io::Result
가 Ok
라면 expect
는 Ok
가 가지고 있는 결과값을 돌려주어 사용할 수 있도록 합니다. 이 경우 결과값은 사용자가 표준 입력으로 입력했던 바이트의 개수입니다.
만약 expect
를 호출하지 않는다면 컴파일은 되지만 경고가 나타납니다.
$ cargo build
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
warning: unused `std::result::Result` which must be used
--> src/main.rs:10:5
|
10 | io::stdin().read_line(&mut guess);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unused_must_use)] on by default
println!("You guessed: {}", guess);
위의 라인은 사용자가 입력한 값을 저장한 문자열을 출력합니다. {}
는 변경자로써 값이 표시되는 위치를 나타냅니다. {}
를 이용해 하나 이상의 값을 표시할 수도 있습니다.
지금까지의 코드(추리게임의 처음 부분)를 cargo run
을 통해 테스트 합니다.
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
다음으로 사용자가 추리하기 위한 비밀번호를 생성합니다. 비밀번호는 매번 달라야합니다. 1에서 100 사이의 임의의 수를 사용하도록 하겠습니다.
Crate
를 사용해 더 많은 기능 가져오기crate
는 러스트의 패키지입니다. rand
crate는 다른 프로그램에서 사용되기 위한 용도인 library crate입니다. rand
를 사용하는 코드를 작성하기 전 Cargo.toml 을 수정하여 rand
크레이트를 의존 리스트에 추가합니다.
[dependencies]
rand = "0.3.14"
dependencies에 추가 후 프로젝트 빌드를 합니다.
$ cargo build
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading rand v0.3.14
Downloading libc v0.2.14
Compiling libc v0.2.14
Compiling rand v0.3.14
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
크레이트를 업그레이드 하고싶다면 Cargo에서 제공하는 update 명령어를 사용할 수 있습니다.
$ cargo update
src/main.rs 를 다음과 같이 업데이트합니다.
extern crate rand;
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
extern crate rand;
를 추가하여 rand crate를 사용할 수 있습니다. 이는 use rand
로도 표기할 수 있습니다. use rand::Rng
에서 Rng
는 정수 생성기가 구현한 메소드들을 정의한 trait입니다.
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
위의 코드는 Rng trait에 정의되어있는 get_range
메소드를 호출합니다. gen_range(1, 101)
는 1부터 100 사이의 임의의 숫자를 생성합니다. 생성된 임의의 숫자는 비밀번호(secret_number)가 되고 두 번째 줄에서 출력됩니다. 두 번째 줄은 최종 버전에서는 삭제됩니다.
프로그램을 실행해보면 매 실행마다 다른 숫자가 나타나는 것을 볼 수 있습니다.
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 7
Please input your guess.
4
You guessed: 4
$ cargo run
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 83
Please input your guess.
5
You guessed: 5
src/main.rs 를 다음과 같이 업데이트합니다.
extern crate rand;
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is: {}", secret_number);
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse()
.expect("Please type a number!");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
std::cmp::Ordering
에서 Ordering
은 Result
와 같은 열거형이지만 Ordering
의 값은 두 개의 값을 비교할 때 나올 수 있는 Less
, Greater
, Equal
입니다. cmp
메소드는 두 값을 비교하고 Ordering
열거형을 반환합니다. 위의 코드를 통해 guess
와 secret_number
를 비교합니다. 또한, match
표현문을 이용해 cmp
의 결과에 따라 무엇을 할 것인지 결정할 수 있습니다.
하지만 guess
는 String
이고 secret_number
는 정수형이므로 guess
를 정수형으로 바꾸기 위해 아래의 코드를 사용합니다.
let guess: u32 = guess.trim().parse()
.expect("Please type a number!");
문자열의 parse 메소드는 문자열을 숫자형으로 파싱합니다. 다양한 종류의 정수형을 반환하기 때문에 let guess: u32처럼 정확한 타입을 명시해야 합니다.
프로그램을 실행합니다.
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.43 secs
Running `target/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
76
You guessed: 76
Too big!
위의 코드는 한 번의 추리만 가능합니다. 반복문을 추가하여 여러 번의 추리를 허용하도록 변경합니다.
extern crate rand;
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
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;
}
}
}
}
loop
키워드는 무한루프를 제공합니다. 추리값을 입력 받는 코드부터 모든 코드들을 반복문 내로 옮깁니다. 사용자가 정답을 맞췄을 때 게임이 종료되도록 break
문도 추가해줍니다. 비밀번호를 출력하는 println!
을 삭제해줍니다.
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
expect
메소드 호출을 match
표현식으로 바꾸는 것은 에러 발생 시 종료에서 처리로 바꾸는 일반적인 방법입니다. parse
가 문자열을 정수로 바꾸지 못했다면 Err
를 반환합니다. 따라서 세 번째 줄 코드인 continue
를 실행하여 loop
의 다음 반복으로 가서 또다른 추리 값을 요청하도록 합니다.
이제 최종 프로그램을 실행해봅니다.
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Running `target/guessing_game`
Guess the number!
Please input your guess.
50
You guessed: 50
Too big!
Please input your guess.
30
You guessed: 30
Too small!
Please input your guess.
40
You guessed: 40
You win!