본문 바로가기

Rust Programming

[Rust] 5. Ticket v2: Enums, Branching(match)

 
이번 chapter에서는 Rust domain modelling skills를 다듬을 것이다.
우리는 아래와 같은 개념들을 배울것이다.

  • enum, Rust의 data modeling에서 강력한 features
  • Option type, nullable values를 model하기 위한
  • Result type, recoverable errors를 model하기 위한
  • Debug and Display traits, printing을 위한
  • Error trait, error types를 표시하기 위한
  • TryFrom and TryInto traits, fallible conversions를 위한
  • Rust의 package system, library가 뭐고, binary가 뭐고, 다른 여러 crates를 사용하는 법

00_intro exercise는 Ticket type을 다듬을 준비가 됐다는 문구였다!


이전 chapter에서 작성했던 validation logic에 의하면,
ticket에는 To-Do, InProgress, Done의 valid statuses가 있다.
Ticket struct의 status field나 new method의 status parameter를 보면 이는 명확하지 않다.

#[derive(Debug, PartialEq)]
pub struct Ticket {
    title: String,
    description: String,
    status: String,
}

impl Ticket {
    pub fn new(title: String, description: String, status: String) -> Self {
        // [...]
    }
}

 
두 가지 경우 모두 status field를 나타내기 위해 String type을 사용한다.
String은 일반적인 type이지만, status에는 몇몇의 값으로 제한되어 있다는 정보를
즉시 알려주지 못한다.
더욱이, Ticket::new의 caller는 status가 valid한지 runtime에만 알 수 있다.
 
이것을 enumerations를 활용해 해결할 것이다.
 


 
enumeration은 variants라 불리는 고정된 값의 집합을 갖는 type이다.
Rust에서, enum keyword를 사용해 enumeration을 정의할 수 있다.

enum Status {
    ToDo,
    InProgress,
    Done,
}

 
enum은 struct와 같이 새로운 Rust type을 정의한다.
 


01_enum exercise는 이전에 ticket struct 및 functions를 구현했던 것에
status를 enum으로 변환하고 어떻게 사용하는지 적용시키는 것이었다.
그 과정에서 비교연산과 clone 및 copy가 필요했으므로 enum type에 derive해줬다.

 


 
enum을 갖고 정확히 무엇을 할 수 있을까?
가장 흔한 operation은 match다.

enum Status {
    ToDo,
    InProgress,
    Done
}

impl Status {
    fn is_done(&self) -> bool {
        match self {
            Status::Done => true,
            // The `|` operator lets you match multiple patterns.
            // It reads as "either `Status::ToDo` or `Status::InProgress`".
            Status::InProgress | Status::ToDo => false
        }
    }
}

 
Rust value를 일련의 patterns와 비교할 수 있는 match statement다.
그냥 if랑 비슷하다고 생각하면 된다.
status가 Done이면, 첫번째 block을 실행하고,
InProgress나 ToDo면, 두번째 block을 실행한다.
 


 
여기서 하나의 핵심 요소가 있다.
match는 exhaustive하다. 
당신은 모든 enum variants를 다뤄야한다.
만약 어떤 variant를 다루는 것을 까먹는다면,
Rust는 compile-time에 error를 낼 것이다.
 
ToDo variant를 까먹었을 때의 예시다.

match self {
    Status::Done => true,
    Status::InProgress => false,
}

 
그러면 compiler는 이렇게 승질을 낸다.

error[E0004]: non-exhaustive patterns: `ToDo` not covered
 --> src/main.rs:5:9
  |
5 |     match status {
  |     ^^^^^^^^^^^^ pattern `ToDo` not covered

 
이건 정말 큰일이다!
Codebases는 시간이 지남에 따라 발전해, 새로운 status(Blocked같은)가 추가될 수 있다.
Rust compiler는 new variant의 모든 단일 match statement에 대해 누락된 logic을 error를 통해 내보낼 것이다.
이게 종종 개발자들이 compiler-driven refactoring을 칭찬하는 이유다.
compiler가 다 알려준다!
선생님인 셈이다.
 


 
만약 당신이 하나 혹은 더 많은 variants에 대해 관심이 다면,
_ pattern을 catch-all(포괄적으로)로서 사용할 수 있다.

match status {
    Status::Done => true,
    _ => false
}

 
_ pattern은 이전 patterns와 일치하지 않는 모든 항목과 일치한다.
 


02_match exercise는 match를 사용하여 enum의 variants에 따라 다른 것을 return하는 문제였다.

 
쉽게 해결했다.
 


 
chapter5의 초반이라 그런지 아직은 행복하다.
근데 진짜 Rust의 compiler는 신이다.
다 알려준다.