숫자 맞히기 게임의 규칙은 다음과 같다. 프로그램이 1부터 100 사이의 임의의 숫자를 생성하면 플레이어는 본인이 생각하는 정답을 입력한다. 입력에 대한 처리를 위한 코드를 저번 시간에 작성했으니 이번 시간에는 임의의 숫자를 생성하는 코드를 작성해보자.
2022.02.01 - [dev-log/Rust] - [숫자 맞히기 게임 만들기] 사용자의 입력을 받고 처리하기
크레이트(crate) 추가하기
- 러스트 표준 라이브러리에서는 난수(random number)를 생성하는 기능을 따로 제공하지 않지만, 러스트 팀이 제공하는 rand 크레이트를 이용하여 난수를 생성할 수 있다.
- 러스트 문서에서는 크레이트를 다음과 같이 설명하고 있다.
"크레이트는 러스트의 compilation unit이다. rustc 어떤_파일.rs 명령어를 실행할 때마다 어떤_파일.rs는 crate 파일로 취급된다. 만약 어떤_파일.rs이 mod 선언(러스트에서 모듈을 정의하는 방식)을 내부에 가지고 있다면, 그 모듈 파일의 내용은 컴파일러를 실행하기 전에 crate 파일에서 mod 선언이 발견되는 장소로 삽입되어진다. 다시 말해, 모듈들은 각각 개별적으로 컴파일 되는 게 아니며, 크레이트만이 컴파일된다. 크레이트는 바이너리 또는 라이브러리로 컴파일할 수 있다. 기본적으로 rustc는 크레이트로부터 바이너리를 생성하는데, 만약 라이브러리를 생성하고 싶다면 --crate-type 플래그로 lib를 설정하면 된다. ( --crate-type=lib, #[crate_type = "lib"] - 참고)"- mod 선언: 러스트에서 모듈을 정의하는 방식으로, 러스트에서 모듈 정의는 모두 mod로 시작한다.
- compilation unit: 하나의 logical unit으로 컴파일되는 소스 코드를 의미함.
- rand 크레이트를 프로젝트에 추가해보자. Cargo.toml의 [dependencies] 섹션에 다음과 같이 코드를 추가해준다.
[dependencies] rand = "0.8.4"
- 카고는 SemVer(시맨틱 버저닝)을 이해한다.
- 글 작성 시점에는 rand의 최신 버전이 0.8.4여서 위와 같이 작성했다. 최신 버전이 몇인지는 여기에서 확인할 수 있다.
- 그런 다음 cargo build 명령어를 실행하여 프로젝트를 빌드해보자. 그럼 아래와 같이 카고가 crates.io로부터 크레이트와 이 크레이트가 의존하고 있는 다른 크레이트들을 다운로드 받은 것을 확인할 수 있다.
- 다시 cargo build 명령어를 실행하면 크레이트를 다시 다운로드 받지 않는다. 왜냐하면 프로젝트를 빌드할 때마다 카고가 Cargo.lock(이 파일은 빌드 명령어를 최초로 실행할 때 생성됨) 파일이 존재하는지를 먼저 확인하고, 이 파일에 기록된 크레이트의 버전을 사용하기 때문이다. 크레이트의 버전을 명시적으로 업그레이드하지 않는 한 말이다.
- 크레이트의 버전을 업그레이드하려면 다음과 같이 update 명령어를 사용하면 된다.
$ cargo update
- 이 명령어를 사용하면 Cargo.lock 파일에 명시된 버전을 무시하고 최신 버전으로 크레이트를 업데이트한 뒤, Cargo.lock 파일에 적힌 버전도 갱신한다.
- 최신 버전 외에 특정한 버전을 사용하고 싶을 경우 Cargo.toml의 [dependencies] 섹션에서 해당 크레이트의 버전 번호를 원하는 버전 번호로 수정한 뒤 다시 빌드를 하면 된다. 마찬가지로 Cargo.lock 파일에 적힌 버전도 갱신된다.
난수 생성하기
- rand 크레이트를 활용하여 난수 생성 코드를 작성해보자. src/main.rs 파일의 코드를 다음과 같이 수정해준다.
use rand::Rng; 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); }
- 실행해보면 다음과 같이 결과가 잘 출력된다. 실행할 때마다 다른 숫자가 나오는 것을 확인할 수 있다.
- 코드를 살펴보자.
- use rand::Rng;
- Rng라는 건 위와 같이 코드 어디에서도 명시적으로 사용하는 데가 없는데 왜 가져오는 걸까?에 대한 의문이 들었다. 일단 Rng는 트레이트(trait)로서, 불특정 타입을 대상으로, 이들이 구현해야 할 메서드의 집합을 정의하는 것이라고 한다. 스택 오버플로우에 이와 관련한 좋은 답변이 있어 참고하자면, rand::thread_rng()는 ThreadRng라는 struct(구조체)를 반환하는데, 그 다음에 호출되는 gen_range() 메서드는 이 ThreadRng라는 구조체에 직접적으로 묶여있는 것이 아니어서 ThreadRng는 gen_range() 메서드가 뭔지 혼자서 알아차릴 수 없다. gen_range() 메서드는 Rng 트레이트에 정의되어 있기는 한데, 완전 무관한 다른 트레이트에도 정의되어있을 가능성이 존재하기 때문에 이렇게 파일 내에서 use 구문을 통해 Rng 트레이트를 보라고 명시적으로 알려줘야 하는 것이다. (답변 작성자가 언급한 것과 같이, 자바스크립트만 봐온 나에게는 이런 부분 하나하나가 신세계다..)
- 아무튼 gen_range() 메서드를 사용하려면 트레이트가 이렇게 현재 scope 내에 선언되어야 한다. (트레이트에 대해선 10장에서 자세히 살펴본다고 한다.)
- let secret_number = rand::thread_rng().gen_range(1..101);
- rand::thread_rng() : Rng 트레이트에 정의되어 있는 메서드로, 난수 생성기를 리턴한다. 이 난수 생성기는 운영체제가 지정한 seed값(난수를 발생하는 데 기준이 되는 값)을 사용하고, 현재 코드를 실행 중인 스레드 내에 존재한다.
- gen_range(1..101) : (시작 값..끝값+1) 형식으로 인자를 전달하여 난수의 범위를 지정할 수 있다. 보다시피 이렇게 1..101을 전달하면 1부터 100까지의 값만 생성된다. 101은 포함되지 않는다.
- use rand::Rng;
- 크레이트 사용 방법은 크레이트의 문서를 통해 알 수 있다. 크레이트 문서를 그때그때 구글링해서 찾아가기 번거로울 수 있다. 이러한 번거로움을 줄여주는 것이 바로 cargo doc --open 명령어다. 이 명령은 프로젝트에서 사용 중인 모든 의존 패키지들이 제공하는 문서들을 로컬에서 빌드하여 브라우저에서 보여준다.
3편에선 난수와 사용자 입력을 비교하는 방법을 알아보자.
참고
'학습 내용 > Rust' 카테고리의 다른 글
[숫자 맞히기 게임 만들기] 정답 맞힐 때까지 다중 입력 지원하기 + 올바르지 않은 입력 처리하기 (0) | 2022.03.09 |
---|---|
[숫자 맞히기 게임 만들기] 난수와 사용자 입력 비교하기 (0) | 2022.02.15 |
[숫자 맞히기 게임 만들기] 사용자의 입력을 받고 처리하기 (4) | 2022.02.01 |
Cargo로 프로젝트 생성하기 (0) | 2022.01.30 |
rustfmt와 RLS, rust-analyzer (0) | 2022.01.28 |