Rust 맛보기

Posted by yunki kim on January 16, 2022

Rust 설치

1
2
$ curl https://sh.rustup.rs -sSf | sh
 
cs

Rust 업데이트

1
2
$ rustup update
 
cs

Rust 설치 확인(버전 확인)

1
2
$ rustc --version
 
cs

Cargo 사용하기

  Cargo는 러스트의 빌스 시스템, 패키지 매니저이다. Cargo를 사용해서 다음과 같이 프로젝트를 생성할 수 있다.

1
2
$ cargo new hello_cargo --bin
 
cs

  위 커맨드에서 --bin은 라이브러리가 아닌 실행 가능한 애플리케이션(binary)를 만든다는 의미이다. hello_cargo는 프로젝트 및 프로젝트 디렉토리 이름이다.

  Cargo는 의존성을 Cargo.toml에서 관리한다.

  프로젝트 빌드 명령어

1
2
$ cargo build
 
cs

  프로젝트 실행

1
2
$ cargo run
 
cs

  프로젝트 컴파일 성공 여부

1
2
$ cargo check
 
cs

    때로는 'cargo check'가 'cargo build'보다 빠르다. 이는 'cargo build'가 실행파일을 생성하는 반면 'cargo check'는 생성하지 않기 때문이다. 따라서 코드를 작성하는 도중 컴파일 성공 여부를 보고 싶다면 'cargo check'가 'cargo build'보다 적절하다.

  릴리즈 빌드

1
2
$ cargo build --release
 
cs

 

숫자 추리 게임을 이용해 문법 살펴보기

  사용자가 입력한 숫자와 컴퓨터가 선택한 숫자의 일치여부를 확인하는 게임을 만들어 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// std라는 표준 라이브러리에 있는 io라이브러리를 스코프로 가져온다
use std::io;
 
fn main() {
    // println!은 string을 화면에 표시하는 매크로
    println!("Guess the number");
 
    println!("Please input your guess.");
 
    // mut(mutable) 변수 선언
    // mut가 없으면 immutable
    // ::는 new가 String 타입의 연관함수 임을 의미
    // 연관 함수는 하나의 타입을 위한 함수이다.
    let mut guess = String::new();
 
    // .read_line()은 &mut guess를 인자로 넘기는 표준 입력
    // &는 참조자
    io::stdin().read_line(&mut guess)
        .expect("Failed to read line");
 
    // {}는 변경자로 이 경우 guess의 값이 들어갈 위치 표시
    println!("You guessed: {}", guess);
}
 
cs

  위 코드에서 read_line은 인자로 넘긴 문자열에 사용자가 입력을 저장하고 값을 반환한다. 여기서 반환되는 값은 io::Result이다. Result타입은 enum이며 Ok, Err 두 개의 variants를 가질 수 있다. Ok는 처리 성공여부이고 Err는 처리실패와 그 이유를 나타낸다. 

  expect()는 io::Result 인스턴스가 Err일 경우 프로그램 작동을 멈추고  인자로 넘긴 메시지를 출력한다. 만약 io::Result가 Ok이면 expect는 Ok가 가지고 있는 결과를 돌려주어 사용할 수 있게 한다.

 

  이제 난수를 생성하기 위해 Cargo.toml에 다음과 같은 의존성을 추가하자. 그 후 cargo build를 통해 의존성을 다운받으면 된다.

1
2
3
4
5
// cargo.toml에서
[dependencies]
 
rand = "0.4.0"
 
cs

  cargo.lock

   cargo.lock은 코드를 빌드할 때 같은 산출물이 나오도록 보장해준다. 예를 들어 같은 패키지를 사용하지만 버전의 차이로 인해 프로그램 실행에 오류가 생기는 경우가 있다. cargo.lock은 이런 오류를 방지해 준다. cargo.lock은 프로젝트를 처음 빌드할때 기준을 만족하는 모든 의존 패키지 버전을 확인하고 cargo.lock에 기록한다. 만약 미래에 프로잭트를 빌드할 경우 cargo는 모든 버전을 다시 확인하는 대신 cargo.lock에 파일 존재 여부를 확인해 그 안에 명시된 버전을 사용한다.

    cargo.lock 예시

1
2
3
4
5
6
7
8
9
10
11
12
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
 
[[package]]
name = "hello-cargo"
version = "0.1.0"
dependencies = [
 "rand",
]
cs

 

숫자 추리 게임 지속하기

  다시 숫자 추리 게임으로 돌아가 난수를 생성하고 사용자가 입력한 수와 일치하는 지를 확인하는 코드를 작성해 보자.

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
// 외부에 의존하는 크레이트가 있음을 알린다.
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);
 
    // Ordering은 Result같은 enum이다.
    // variants로는 Less, Greater, Equal이 존재한다.
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You Win!"),
    }
}
 
cs

  match는 arm으로 이뤄져 있다. 하나의 arm은 하나의 패턴과 match표현식에서 주어진 값이 패턴과 맞는다면 실행할 코드로 이뤄져있다. 러스트는 match에 주어진 값이 arm의 패턴에 맞는지 순서대로 확인한다. 

  위의 코드를 실행하면 에러가 나온다. 이는 러스트가 강타입이기 때문이다. 위 코드에서 guess는 String타입인 반면에 secret_number는 정수형이다. 따라서 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 외부에 의존하는 크레이트가 있음을 알린다.
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);
 
    // 무한 루프
    loop {
        println!("Please input your guess.");
 
        let mut guess = String::new();
 
        io::stdin().read_line(&mut guess)
            .expect("Failed to read line");
 
        // 문자열이 정상적으로 정수로 변환되었으면 Ok,
        // 에러 발생시 Err
        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 => { // guess와 secret_number가 같으면 루프 종료
                println!("You Win!");
                break;
            }
        }
 
    }
 
}
 
cs

 

제어문

1
2
3
4
5
6
7
8
9
10
11
fn main() {
    let number = 3;
 
    if number < 3 {
        println!("number is smaller than three");
    } else if number > 3 {
        println!("number is larger than three");
    } else {
        println!("number is three");
    }
}
cs

  let 구문에서 if 사용

    if는 표현식이기 때문에 다음과 같이 사용할 수 있다.

1
2
3
4
5
6
7
8
fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };
}
cs

  하지만 조건에 따라 다른 타입의 값을 반환할 수는 없다. 이는 러스트가 컴파일 시 타입을 특정지을 수 없기 때문이다.

1
2
3
4
5
6
7
8
9
fn main() {
    let condition = true;
    // error[E0308]: if and else have incompatible types
    let number = if condition {
        5
    } else {
        '6'
    };
}
cs

 

반복문

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
    // 무한 루프
    loop {}
 
    let number = 3;
    while number != 0 {}
 
    // for를 사용한 콜렉션 순환
    let a = [102030];
    for element in a.iter() {}
 
    // 1~3까지 순환, rev(): 역순
    for number in (1..4).rev() {
        println!("{}", number);
    }
}
cs