본문 바로가기
학습 내용/Rust

[숫자 맞히기 게임 만들기] 난수와 사용자 입력 비교하기

by yein 2022. 2. 15.

이전 시간에 rand 크레이트를 이용하여 생성한 난수와, 사용자가 입력한 값을 비교하고 비교 결과에 대해 출력하는 코드를 작성해보자.

 

먼저, 이전까지 작성해둔 코드에 표준 라이브러리로부터 std::cmp::Ordering 타입을 가져오고, match 표현식을 추가해준다.

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("숫자를 맞혀봅시다!");

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

    println!("사용자가 맞혀야 할 숫자: {}", secret_number); // TODO: 테스트 후 제거할 코드

    println!("정답이라고 생각하는 숫자를 입력하세요.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("입력한 값을 읽지 못했습니다.");

    println!("입력한 값: {}", guess);

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("입력한 숫자가 작습니다!"),
        Ordering::Greater => println!("입력한 숫자가 큽니다!"),
        Ordering::Equal => println!("정답!"),
    }
}

일단 이 코드는 비교하려는 두 값 사이에 타입 불일치 문제가 있어서 아직 컴파일이 안된다. 타입을 일치시켜주는 작업은 곧 하기로 하고, 우선은 작성한 코드부터 먼저 살펴보자.

 

먼저 cmp는 비교가 가능한 두 값을 정렬하고 비교할 수 있는 다양한 도구를 갖춘 모듈로, 이렇게 두 값을 비교한 결과가 바로 Ordering 열거자(enums)다. Ordering 열거자의 열거값(variants)으로는 Less(비교되는 값 < 기준값), Equal(비교되는 값 == 기준값), Greater(비교되는 값 > 기준값) 이렇게 세 가지가 있다. 예제의 match 표현식에서 보면 guess 변수(사용자 입력값)가 비교되는 값이고 secret_number 변수가 기준값이 된다. cmp 메서드는 두 값의 비교 결과에 따라 이러한 세 가지 열거값 중 하나를 반환한다.

 

match 표현식은 패턴 일치 여부에 기반하여 흐름을 제어하며, 여러 개의 match arm으로 구성된다. 각각의 match arm은 다음과 같이 두 가지 부분으로 나뉜다.

  • 패턴(pattern)
  • 패턴이 일치할 때 실행될 코드
    • 패턴과 어떤 것이 일치할 때를 의미하는가? : match 표현식의 시작 부분에 주어진 값 (위 코드를 예로 들면 guess.cmp(&secret_number))

예제의 match 표현식의 match arm 중 하나인 Ordering::Less => println!("입력한 숫자가 작습니다!")를 예로 들면, 화살표를 기준으로 Ordering::Less는 패턴이고 println!("입력한 숫자가 작습니다!")는 패턴과 값이 일치할 때 실행될 코드다. match 표현식에 대해선 뒷장에서 자세히 살펴본다고 한다.

 

이제 위 코드의 타입 불일치 문제에 대해 살펴보자.

먼저 러스트는 타입 추론(type interference)을 지원한다. 그래서 guess 변수에 명시적으로 String 타입을 지정하지 않았음에도 불구하고 let mut guess = String::new(); 코드를 보고 String 타입임을 추론한다. 마찬가지로 secret_number 변수의 타입도 러스트가 추론을 하게 되는데, 1부터 100까지의 값을 갖는 몇 가지 숫자 타입 중 러스트의 디폴트 숫자 타입인 i32 타입이라고 추론한다. '몇 가지 숫자 타입'은 다음과 같다.

  • i32(signed 32-bit number) : 32bit 정수
  • u32(unsigned 32-bit number) : 32bit 부호 없는 정수로, 작은 범위의 양수를 처리하기에 알맞음.
  • i64(signed 64-bit number) : 64bit 정수
  • 등..

결국 에러는 guess 변수와 secret_number 변수 간의 타입 불일치 문제로 인해 발생한 것이다. guess 변수를 숫자로 변환하기 위해, match 표현식 이전에 다음과 같이 코드를 추가해준다.

...

io::stdin()
    .read_line(&mut guess)
    .expect("입력한 값을 읽지 못했습니다.");

let guess: u32 = guess
    .trim()
    .parse()
    .expect("입력한 값이 올바른 숫자가 아닙니다.");

println!("입력한 값: {}", guess);
    
...

trim() 메서드는 문자열의 앞뒤 공백을 제거한다. (read_line 메서드 쪽에서 사용자가 값을 입력할 때 마지막으로 입력한 엔터를 제거하기 위해 필요)

parse() 메서드는 문자열을 파싱하여 숫자로 변환한다. 이 메서드는 다양한 타입의 숫자를 파싱할 수 있기 때문에, 변환하길 원하는 타입을 명시적으로 알려줘야 한다. 그러기 위해서 콜론(:)으로 guess 변수의 타입을 u32로 명시적으로 지정해주었다.

마지막으로 parse 메서드가 에러를 반환할 수도 있으므로 이에 대한 처리를 하기 위해 이전에 살펴본 것과 마찬가지로 expect 메서드를 사용하여 잠재적인 오류를 처리해준다.

 

이렇게 u32 타입으로 변환한 guess 변수값과 secret_number 변수값을 비교하게 되므로, secret_number 변수값도 결과적으로 u32 타입으로 추정되게 된다. 그리고 이제 비교되는 두 값의 타입이 일치하게 되었으므로 비교가 가능해진다.

 

cargo run 명령어를 통해 프로그램을 다시 실행해보자.

 

 

위 움짤(?)을 보면 알겠지만, 기능 자체는 정상적으로 동작하나 사용자가 입력을 한 번밖에 하지 못한다는 문제가 있다.

다음 편에선 반복문을 추가하여 사용자가 값을 여러번 입력할 수 있도록 보완해보자~

 

 


참고

https://rinthel.github.io/rust-lang-book-ko/ch02-00-guessing-game-tutorial.html