DaleSchool

첫 번째 프로그램: 숫자 맞추기 게임

입문20분

학습 목표

  • 지금까지 배운 문법을 종합하여 하나의 프로그램을 완성할 수 있다
  • Cargo로 새 프로젝트를 생성하고 빌드할 수 있다
  • 외부 크레이트(rand)를 Cargo.toml에 추가할 수 있다
  • 사용자 입력을 받아서 처리할 수 있다

Rust를 내 컴퓨터에 설치하기

지금까지는 Rust Playground에서 코드를 실행했어요. 이번 모듈부터는 내 컴퓨터에서 직접 프로그램을 만들어볼 거예요!

터미널에서 아래 명령어를 실행하면 Rust가 설치돼요.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Windows라면 rustup.rs에서 설치 파일을 다운로드하세요.

설치가 끝나면 확인해볼까요?

rustc --version
cargo --version

버전 번호가 나오면 성공이에요!

Cargo — Rust의 도구 모음

Cargo는 Rust의 빌드 시스템이자 패키지 매니저예요. 프로젝트 생성, 빌드, 실행, 외부 라이브러리 관리까지 모두 Cargo가 해줘요.

새 프로젝트를 만들어볼게요.

cargo new guessing_game
cd guessing_game

이 명령어를 실행하면 이런 폴더 구조가 만들어져요.

guessing_game/
├── Cargo.toml    # 프로젝트 설정 파일
└── src/
    └── main.rs   # 코드 파일

src/main.rs를 열면 이미 "Hello, world!"가 들어있어요. cargo run으로 실행해보세요!

cargo run

동작하는 코드: 숫자 맞추기 게임

이제 본격적으로 게임을 만들어볼게요. 먼저 랜덤 숫자를 만들기 위해 rand 크레이트(crate)를 추가해야 해요. 크레이트는 다른 사람이 만든 라이브러리예요.

cargo add rand

이 명령어가 Cargo.tomlrand를 자동으로 추가해줘요.

아래 코드를 src/main.rs에 붙여넣고 cargo run으로 실행해보세요!

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

fn main() {
    println!("숫자 맞추기 게임!");
    let secret = rand::thread_rng().gen_range(1..=100);

    loop {
        println!("숫자를 입력하세요 (1~100):");

        let mut guess = String::new();
        io::stdin().read_line(&mut guess).unwrap();

        let guess: i32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("숫자를 입력해주세요!");
                continue;
            }
        };

        match guess.cmp(&secret) {
            Ordering::Less => println!("더 커요!"),
            Ordering::Greater => println!("더 작아요!"),
            Ordering::Equal => {
                println!("정답!");
                break;
            }
        }
    }
}

실행하면 숫자를 입력하라는 메시지가 나와요. 숫자를 입력하면 정답보다 큰지 작은지 알려주고, 맞출 때까지 반복해요!

한 줄씩 이해하기

코드가 좀 길죠? 하나씩 뜯어볼게요.

1단계: 가져오기

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

use는 다른 곳에 있는 기능을 가져오는 키워드예요.

  • std::io — 사용자 입력/출력 기능
  • std::cmp::Ordering — 두 값을 비교한 결과 (Less, Greater, Equal)
  • rand::Rng — 랜덤 숫자를 만드는 기능 (우리가 추가한 크레이트)

2단계: 랜덤 숫자 생성

let secret = rand::thread_rng().gen_range(1..=100);

1부터 100 사이의 랜덤 숫자를 하나 만들어서 secret에 저장해요. 1..=100은 for 반복문에서 배운 범위 문법과 같아요!

3단계: 사용자 입력 받기

let mut guess = String::new();
io::stdin().read_line(&mut guess).unwrap();

String::new()로 빈 문자열을 만들고, read_line()으로 사용자가 입력한 텍스트를 저장해요. unwrap()은 "에러가 나면 프로그램을 멈춰라"라는 뜻이에요. 에러 처리는 나중에 제대로 배울 거예요.

4단계: 문자열을 숫자로 변환

let guess: i32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => {
        println!("숫자를 입력해주세요!");
        continue;
    }
};

사용자가 입력한 건 문자열이에요. 숫자와 비교하려면 i32로 바꿔야 해요.

  • trim() — 입력 끝에 붙는 줄바꿈 문자를 제거
  • parse() — 문자열을 숫자로 변환 시도
  • match — 변환 결과에 따라 분기 (성공하면 Ok, 실패하면 Err)

같은 이름 guess를 다시 let으로 선언한 거 보이시나요? 모듈 02에서 배운 섀도잉이에요! 문자열 guess를 숫자 guess로 바꾼 거예요.

5단계: 비교하기

match guess.cmp(&secret) {
    Ordering::Less => println!("더 커요!"),
    Ordering::Greater => println!("더 작아요!"),
    Ordering::Equal => {
        println!("정답!");
        break;
    }
}

cmp()는 두 값을 비교해서 Less(작음), Greater(큼), Equal(같음) 중 하나를 돌려줘요. match로 각 경우에 맞는 메시지를 출력하고, 정답이면 break로 반복을 끝내요.

"왜?" — 여기까지 오셨으면 Rust 기본기를 갖춘 거예요!

이 프로그램에 지금까지 배운 것이 다 들어있어요.

  • 변수와 가변성: let, let mut
  • 타입: i32, String
  • 함수 호출: println!(), read_line(), parse()
  • 조건 분기: match (if보다 강력한 분기 방법!)
  • 반복문: loop + break, continue
  • 범위: 1..=100

처음엔 복잡해 보여도, 하나씩 뜯어보면 이미 아는 것들의 조합이에요. Phase 2에서는 소유권, 구조체, 에러 처리 같은 Rust만의 독특한 개념들을 배울 거예요. 기대하세요!

심화 학습

Cargo.toml — 프로젝트 설정 파일 살펴보기

Cargo.toml을 열어보면 이런 내용이 있어요.

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"

[dependencies]
rand = "0.8"
  • [package] — 프로젝트 이름, 버전, Rust 에디션
  • [dependencies] — 외부 크레이트 목록. cargo add rand를 실행하면 여기에 자동으로 추가돼요

자주 쓰는 Cargo 명령어도 알아둘게요.

| 명령어 | 하는 일 | | -------------------- | ------------------------------------ | | cargo new 이름 | 새 프로젝트 생성 | | cargo run | 빌드 + 실행 (개발 중 가장 많이 사용) | | cargo build | 빌드만 (실행은 안 함) | | cargo check | 컴파일 가능한지 빠르게 검사 | | cargo add 크레이트 | 외부 크레이트 추가 |

match — if보다 강력한 분기

이 게임에서 match가 두 번 나와요. matchif/else if와 비슷하지만, 모든 경우를 빠짐없이 처리해야 해요.

fn main() {
    let number = 3;

    match number {
        1 => println!("하나"),
        2 => println!("둘"),
        3 => println!("셋"),
        _ => println!("다른 숫자"), // 나머지 모든 경우
    }
}

_는 "그 외 나머지"를 뜻해요. match에 대해서는 Phase 2에서 훨씬 더 자세히 배울 거예요.

숫자가 아닌 값을 parse()하면 에러가 발생해요. 우리 게임에서는 match로 이걸 처리하고 있어요.

만약 match 대신 unwrap()을 썼다면?

// 이렇게 하면 숫자가 아닌 입력 시 프로그램이 멈춰요!
let guess: i32 = guess.trim().parse().unwrap();

"abc"를 입력하면 이런 에러가 나요.

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
ParseIntError { kind: InvalidDigit }'

해석: "숫자가 아닌 값을 숫자로 바꾸려 했어요"라는 뜻이에요. unwrap() 대신 match를 쓰면 에러 상황을 우아하게 처리할 수 있어요.

미니 프로젝트: 게임 업그레이드

기본 게임이 잘 돌아가면, 아래 기능을 하나씩 추가해보세요!

  1. 시도 횟수 세기: 몇 번 만에 맞췄는지 출력하기. 힌트: mut 변수로 카운터를 만드세요.
  2. 범위 제한: 1~100 범위 밖의 숫자를 입력하면 "1에서 100 사이의 숫자를 입력해주세요!"라고 안내하기.
  3. 난이도 선택: 게임 시작 전에 범위를 선택할 수 있게 만들기. (예: 1~10, 1~100, 1~1000)

Q1. Cargo로 새 프로젝트를 만드는 명령어는?

  • A) cargo init project
  • B) cargo new project
  • C) cargo create project
  • D) cargo start project

Q2. 아래 코드에서 guess.trim().parse()의 역할은?

let guess: i32 = guess.trim().parse().unwrap();
  • A) 문자열을 대문자로 바꾼다
  • B) 공백을 제거하고 숫자로 변환한다
  • C) 문자열을 거꾸로 뒤집는다
  • D) 문자열을 복사한다

Q3. match에서 _는 무엇을 의미할까요?

  • A) 에러가 발생한다
  • B) 아무것도 하지 않는다
  • C) 나머지 모든 경우를 처리한다
  • D) 반복을 멈춘다