갈루아의 반서재

1. 난수 발생기



다음으로 해야할 일은 비밀 숫자를 만들어내는 것이다. Rust 는 아직까지는 난수를 발생하는 기능을 표준 라이브러리에 포함하지 않고 있다. 하지만 rand crate 를 제공하고 있으므로, 이 패키지를 이용하면 된다.

외부 크레이트를 사용하기 위해서는 Cargo.toml 을 수정해야 한다. 파일을 오픈하고 dependencies 아래에 아래와 같이 rand 크레이트의 정보를 입력해주면 된다.


[dependencies]

rand="0.3.0"


Cargo.toml 파일의 [dependencies] 는 일종의 패키지와 같다. Cargo 는 dependencies 섹션을 통해 어떤 외부 크레이트와 크레이트의 버전을 요구하는지 파악한다. 이 경우 0.3.0 이 의미하는 것은 0.3.0 과 호환되는 버전이라는 의미이다.

정확하게 0.3.0 버전만 사용하고 싶다면, rand="=0.3.0" (등호가 2개임에 유의해야한다) 와 같이 입력해야 한다. 그리고 최신 버전을 이용하고 싶다면 rand="*" 와 같이 입력하면 된다. 그리고 범위를 설정할 수도 있다. 더욱 자세한 내용은 Cargo’s documentation 을 참조하면 된다.

만약 dependencies 에 외부 크레이트를 지정하지 않으면 아래와 같은 에러가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bourne@vikander MINGW64 ~/git/rust-projects/rust-projects/guessing_game (master)
$ cargo run -v
   Compiling guessing_game v0.1.0 (file:///C:/Users/vikander/git/rust-projects/rust-projects/guessing_game)
     Running `rustc src\\main.rs --crate-name guessing_game --crate-type bin --C metadata=00d790d7768b91fc --out-diinfo,link -L dependency=C:\\Users\\vikander\\git\\rust-projects\\rust-projects\\guessing_game\\target\\debug\\deps`
error[E0425]: unresolved name `rand::thread_rng`
 --> src\main.rs:9:25
  |
9 |     let secret_number = rand::thread_rng().gen_range(1101);
  |                         ^^^^^^^^^^^^^^^^
 
error: aborting due to previous error
 
error: Could not compile `guessing_game`.
 
Caused by:
  Process didn't exit successfully: `rustc src\main.rs --crate-name guessing_game --crate-type bin -g -C metadata=00dug --emit=dep-info,link -L dependency=C:\Users\vikander\git\rust-projects\rust-projects\guessing_game\target\debug\deps`
cs


입력 후 다시 실행해보면 아래와 같이 난수를 발생하는 부분이 작동함을 알 수 있다.

1
2
3
4
5
6
7
8
9
bourne@vikander MINGW64 ~/git/rust-projects/rust-projects/guessing_game (master)
$ cargo run
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target\debug\guessing_game.exe`
Guess the number!
The secret number is: 71
Please input your guess.
66
You guessed: 66
cs


그러면 만약 다음주에 최신 버전인 0.3.15 가 출시되고, 이 버전의 경우 현재의 코드에 오류를 가져올 수 있는 내용을 포함하고 있다면 어떻게 되는가? 그 비밀은 Cargo.lock 파일에 있다. 아래는 Cargo.lock 파일의 내용이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root]
name = "guessing_game"
version = "0.1.0"
dependencies = [
 "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
 
[[package]]
name = "libc"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
 
[[package]]
name = "rand"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
 
[metadata]
"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8"
"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5"
 
cs

최초로 빌드할 때 Cargo 는 범위내의 모든 버전을 찾아서 Cargo.lock 파일에 기록한다.다음 번에 해당 프로젝트를 빌드할 때 Cargo 는 Cargo.lock 파일을 체크한 후, 특정 버전을 사용한다.

만약 0.3.15 버전을 사용하고 싶다면 Cargo 는 update 라는 명령을 이용한다. 즉,  lock을 무시하고 우리가 명시한 버전에 맞는 가장 최신 버전을 사용하라는 명령인 것이다. 만약 정상적으로 작동한다면 이 버전들은 lock 파일에 기록된다.

하지만 기본적으로 Cargo 는 0.3.0 보다는 크고 0.4.0 보다는 작은 것만 찾는다. 만약 0.4.x 버전 이상으로 업데이트하고 싶다면 명시적으로 Cargo.toml 을 수정해야 한다. 다음번 build 에서 Cargo 는 인덱스를 업데이트하고 rand 요구사항을 재평가한다.



2. 비밀 숫자 만들기



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
extern crate rand;
 
use std::io;
use rand::Rng;
 
fn main() {
    println!("Guess the number!");
 
    let secret_number = rand::thread_rng().gen_range(1101);
 
    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);
}
cs


Line 1 :

extern crate rand;

가장 먼저 눈에 띄는 것은 첫번째 라인의 extern crate rand 부분이다. [dependencies] 부분에 rand 를 선언했기 때문에 extern crate 를 통해 해당 크레이트를 사용할 것이라는 것을 Rust 에게 알려줘야 한다. use rand 와 동일한 것이다.


Line 4 :

use rand::Rng;

rand 의 Rng 메서드를 사용하겠다는 것이다.


Line 9 & 11 :

   let secret_number = rand::thread_rng().gen_range(1, 101);

    println!("The secret number is: {}", secret_number);

난수발생기를 복사하기 위해 rand::thread_rng() 함수를 사용한다. 그리고 앞에서 use rand::Rng 를 선언했기 때문에 gen_range() 메서드를 이용할 수 있다. 이 메서드는 2개의 인수를 받아서 해당 범위 내에서 난수를 발생시킨다. 여기서 낮은 수인 1은 포함되지만 높은 수인 101은 포함되지 않는다. 즉, (1, 101)의 경우는 최소 1에서 최대 100의 난수를 발생시키는 것이다.

11번째 라인은 비밀 숫자를 출력하는 부분이다. 프로그램이 제대로 만들어지고 있는지 확인하는데 있어 이러한 부분은 꼭 필요하다. 하지만 최종 버전에서는 삭제를 해야한다.

여기까지 작성한 후 실행을 해보자.

1
2
3
4
5
6
7
8
9
10
bourne@vikander MINGW64 ~/git/rust-projects/rust-projects/guessing_game (master)
$ cargo run
   Compiling guessing_game v0.1.0 (file:///C:/Users/KAIS/git/rust-projects/rust-projects/guessing_game)
    Finished debug [unoptimized + debuginfo] target(s) in 0.53 secs
     Running `target\debug\guessing_game.exe`
Guess the number!
The secret number is: 5
Please input your guess.
55
You guessed: 55
cs



3. 추측값 비교하기


이제 입력받은 값을 정답과 비교해보는 부분이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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(1101);
 
    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);
 
    match guess.cmp(&secret_number) {
        Ordering::Less    => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal   => println!("You win!"),
    }
}
cs


Line 4 :

use std::cmp::Ordering;

가장 먼저 std::cmp::Ordering 을 새롭게 불러온 것을 확인할 수 있다.


Line 23~27 :

match guess.cmp(&secret_number) {
    Ordering::Less    => println!("Too small!"),
    Ordering::Greater => println!("Too big!"),
    Ordering::Equal   => println!("You win!"),
}

하단에 새롭게 5줄이 추가되었다. cmp() 메서드는 비교가 가능한 것이라면 어디든지 호출될 수 있으며, Ordering 타입을 결과로 반환한다. 그리고 어떤 종류의 Ordering 인지를 결정하기 위해 match 구문을 사용한다. 그리고 Ordering 은 열거형(enumeration)이다.

OrderingLess, Equal, Greater 이상의 3가지 변수를 가진다. 그리고 match문은 가능한 값에 대해 'arm'을 생성한다. 3가지 타입의 Ordering이 가능하므로 3가지의 'arm'을 가진다.

match guess.cmp(&secret_number) {
    Ordering::Less    => println!("Too small!"),
    Ordering::Greater => println!("Too big!"),
    Ordering::Equal   => println!("You win!"),
}

만약 정답과 비교해 작다면 Too small! 을 출력하고, 더 크다면 Too big 을 출력한다. 그리고 같다면 You win!이라는 메시지를 출력한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bourne@vikander MINGW64 ~/git/rust-projects/rust-projects/guessing_game (master)
$ cargo build
   Compiling guessing_game v0.1.0 (file:///C:/Users/KAIS/git/rust-projects/rust-projects/guessing_game)
error[E0308]: mismatched types
  --> src\main.rs:23:21
   |
23 |     match guess.cmp(&secret_number) {
   |                     ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integral variable
   |
   = note: expected type `&std::string::String`
   = note:    found type `&{integer}`
 
error: aborting due to previous error
 
error: Could not compile `guessing_game`.
 
To learn more, run the command again with --verbose.
cs



4. 타입이 맞지 않는다.


이런, 타입이 맞지 않는다는 에러가 발생했다. let guess = String::new() 라고 썼을 때 Rust는 guess는 문자열이라고 추측했다. 그런데 secret_number는 1과 100사이의 값을 가지는 정수 타입으로 기본적으로 32-bit 숫자이다. 그리고 이렇게 상이한 타입을 2개의 값을 비교하다보니 위와 같은 에러가 발생한 것이다. 그러므로 우리는 문자열인 guess 를 숫자로 변환시켜야 한다. 아래를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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(1101);
 
    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!"),
    }
}
cs


Line 21~22 :

    let guess: u32 = guess.trim().parse()
        .expect("Please type a number!");

새로운 라인 2개가 추가되었다. 이미 guess 변수가 선언이 되었지만, 변환을 위해 위와 같이 다시 선언(Shadowing)할 수 있다. guess_strguess 처럼 서로 다른 2개의 이름을 사용할 것이 아니라 Shadowing 을 통해 그 이름을 다시 사용하는 것이다.

guess.trim().parse()

여기서 guess 는 앞서 선언한 문자열의 guess 를 참조한다. trim() 메서드를 통해 우선 문자열에 존재하는 공백을 제거한다. 이 과정이 특히 중요한데, 예를 들어 우리가 5 라고 입력을 하면, read_line() 을 통한 guess 의 값은 5\n 처럼 보이게 되는데 - 여기서 \n 은 새로운 줄을 의미한다 - trim() 을 통해 이런 부분을 제거하여 5만 남기도록 하기 때문이다.

그리고 parse() 메서드는 문자열을 파싱해서 숫자 형태로 집어넣는다. 여러가지 형태의 숫자로 파싱이 가능한데, 위에서 우리는 정확인 u32 unsigned thirty-two-bit number 형태를 지정했다.

앞에서와 마찬가지로 expect() 메서드를 통해 에러를 처리했다. 이제 실행해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bourne@vikander MINGW64 ~/git/rust-projects/rust-projects/guessing_game (master)
$ cargo run
   Compiling guessing_game v0.1.0 (file:///C:/Users/vikander/git/rust-projects/rust-projects/guessing_game)
    Finished debug [unoptimized + debuginfo] target(s) in 0.55 secs
     Running `target\debug\guessing_game.exe`
Guess the number!
The secret number is: 39
Please input your guess.
84
You guessed: 84
Too big!
 
bourne@vikander MINGW64 ~/git/rust-projects/rust-projects/guessing_game (master)
$ cargo run
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target\debug\guessing_game.exe`
Guess the number!
The secret number is: 37
Please input your guess.
37
You guessed: 37
You win!
 
bourne@vikander MINGW64 ~/git/rust-projects/rust-projects/guessing_game (master)
$ cargo run
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target\debug\guessing_game.exe`
Guess the number!
The secret number is: 42
Please input your guess.
35
You guessed: 35
Too small!
 
cs


3가지 경우에 대해 모두 정상적으로 작동한다. 이제 마지막으로 남은 단계는 정답을 맞출 때까지 위의 과정을 반복하도록 하는 것이다. 다음 편에서 살펴보자.