본문 바로가기

Rust Programming

[Rust] 5. Ticket v2: Variants with data, Branching(if let and let/else), Nullability

enum Status {
    ToDo,
    InProgress,
    Done,
}

 

위와 같은 Status enum은 C-style enum이라고 부른다.

각 variant는 named constant와 약간 유사한 간단한 label이다.

당신은 C, C++, Java, C#, Python 등과 같은 많은 programming languages에서 이러한 enum을 찾을 수 있다.

 

그러나 Rust enum은 무언가 더 할 수 있다.

각 variant에 data를 붙일 수 있다.

 


 

우리가 ticket작업을 하는 사람의 이름을 저장하길 원한다고 하자.

우리는 ticket이 in progress인 경우만 이 정보를 얻을 수 있다.

to-do ticket이나 done ticket일 경우는 정보를 얻을 수 없을 것이다.

우리는 InProgress variant에 String field를 붙임으로써, 이를 model할 수 있다.

enum Status {
    ToDo,
    InProgress {
        assigned_to: String,
    },
    Done,
}

 

InProgress는 이제 struct-like variant다.

사실, struct를 정의할 때 사용했던 방식과 같은 문법을 쓴다.

enum안에 variant로서 inlined된 것 뿐이다.

 


 

만약 우리가 다음과 같이 Status instance의 assigned_to에 access하려 한다면

let status: Status = /* */;

// This won't compile
println!("Assigned to: {}", status.assigned_to);

 

compiler는 투정부린다.

error[E0609]: no field `assigned_to` on type `Status`
 --> src/main.rs:5:40
  |
5 |     println!("Assigned to: {}", status.assigned_to);
  |                                        ^^^^^^^^^^^ unknown field

 

assigned_to는 variant-specific이다.

모든 Status instances에서 사용할 수 있는 것이 아니다.

assigned_to에 접근하기 위해서, 우리는 pattern matching을 사용할 필요가 있다.

match status {
    Status::InProgress { assigned_to } => {
        println!("Assigned to: {}", assigned_to);
    },
    Status::ToDo | Status::Done => {
        println!("Done");
    }
}

 

Status::InProgress { assigned_to } match pattern에서, assigned_to는 binding이다.

우리는 Status::InProgress variant를 destructuring하고,

new variable(assigned_to라는 이름의)에 assigned_to field를 binding한다.

만약 우리가 원한다면, 우리는 다른 variable name으로 field를 bind할 수 있다.

match status {
    Status::InProgress { assigned_to: person } => {
        println!("Assigned to: {}", person);
    },
    Status::ToDo | Status::Done => {
        println!("Done");
    }
}

03_variants_with_data exercise는 status의 variants data가 특정 조건일 때, statement를 수행하고
아닐시에는 전부 panic내버리는 것이었다.

 

String type을 &str로 변환할 때 &*(String name)이렇게 하면 된다.

그냥 str로 변환하려면 *(String name)이렇게 하면 되고...

 


 

이전 exercise의 답은 아마 이와 비슷할 것이다.

impl Ticket {
    pub fn assigned_to(&self) -> &str {
        match &self.status {
            Status::InProgress { assigned_to } => assigned_to,
            Status::Done | Status::ToDo => {
                panic!("Only `In-Progress` tickets can be assigned to someone")
            }
        }
    }
}

 

당신은 Status::InProgress variant에 대해서만 고려하면된다.

정말로 모든 variants를 match시켜야할까?

 


 

if let 구조는 enum의 다른 모든 variants를 다룰 필요없이

하나의 variant에 대해 match를 할 수 있게 해준다.

 

assigned_to method를 간결하게 하기 위해 if let을 다음과 같이 사용하면 된다.

impl Ticket {
    pub fn assigned_to(&self) -> &str {
        if let Status::InProgress { assigned_to } = &self.status {
            assigned_to
        } else {
            panic!("Only `In-Progress` tickets can be assigned to someone");
        }
    }
}

 

else branch가 return early(panic과 같이)하다면,

let/else 구문을 사용할 수 있다.

impl Ticket {
    pub fn assigned_to(&self) -> &str {
        let Status::InProgress { assigned_to } = &self.status else {
            panic!("Only `In-Progress` tickets can be assigned to someone");
        };
        assigned_to
    }
}

 

어떤 right drift를 일으키지 않고 destructured variable을 할당할 수 있다.

즉, variable은 이전 code와 같은 level에서 할당된다.

그래서 assigned_to를 사용할 수 있다.

 


 

if let과 let/else는 관용적인 Rust 구문이다.

당신이 보기에 code의 readability를 향상시킬 수 있다면 사용하면되고,

아니라면 사용하지 않으면 된다.

 


 

04_if_let exercise는 if let을 사용하여, 하나의 경우에만 match하여 statement를 실행하고

나머지는 panic하는 것이었다.

 


 

assigned method에 대한 우리의 구현은 상당히 별로다.

to-do와 done에 대해 panicking하는 것은 이상과 멀다.

Rust의 option type을 사용해보자!

 


 

option은 nullable values를 나타내는 Rust type이다.

그것은 Rust의 standard library에 정의된 enum이다.

enum Option<T> {
    Some(T),
    None,
}

 

option은 value가 present(Some(T))하거나 absent(None)할 수 있다는 생각을 encode한다.

또한 두 cases모두 explicitly하게 다뤄야한다.

만약 당신이 nullable value를 다루는데 , None case를 다루는 것을 까먹었다면,

compile error가 뜰 것이다.

이는 null check를 잊어버려 runtime error가 발생할 수 있는

다른 언어의 implicitly nullability에 비해 많이 발전한 형태다.

 


 

option의 정의는 이전에 보지못한 Rust 구조를 사용한다.

그것은 tuple-like variants다.

 


 

option은 Some(T)와 None 두 variants를 가진다.

Some은 unnamed fields를 가진 variant를 일컫는 tuple-like variant다.

 

Tuple-like variants는 하나의 저장할 field가 있을 때,

특히 option과 같은 wrapper type을 볼 때, 자주 사용한다.

 


 

이는 enums에만 국한되지 않는다.

tuple-like structs도 정의할 수 있다.

struct Point(i32, i32);

 

positional index를 사용해 Point instance의 두 개의 fields에 접근할 수 있다.

let point = Point(3, 4);
let x = point.0;
let y = point.1;

 


 

아직 tuples에 대해 보지 않았기 때문에, tuple-like라는 것이 기이할 수 있다.

Tuples는 primitive Rust type 중 하나다.

types를 사용하여 고정된 수의 values를 그룹화한다.

// Two values, same type
let first: (i32, i32) = (3, 4);
// Three values, different types
let second: (i32, u32, u8) = (-42, 3, 8);

 

문법은 간단하다.

당신은 values의 types를 괄호안에 comma로 구분해 list하면된다.

당신은 dot notation과 field index를 통해 tuple의 fields에 접근할 수 있다.

assert_eq!(second.0, -42);
assert_eq!(second.1, 3);
assert_eq!(second.2, 8);

 

Tuples는 어떤것 전용 struct type을 정의할 필요가 없을 때,

values를 그룹화하는 편리한 방법이다.

 


 

05_nullability exercise는 if let else 구문과 Option을 사용하여 구현했다.

 

compiler가 하라는대로 하니까 된다.

 


 

Rust에는 기능이 참 많은 것 같다.

그런데 전부 compiler와 관련된 기능이며,

run time에 시스템적으로 error날 일은 거의 없다고 볼 수 있을 것 같다.