티스토리 뷰

book 에도 나오는 내용일텐데 그냥 설명하고 싶어졌습니다.

rust 의 enum 에 값 넣기

rust 에서 enum 은 평범하게 다른 언어의 enum 처럼 쓸 수도 있지만,

// rust
enum MyEnum {
    A,
    B
}

rust 의 enum 에는 값이나 필드를 넣을수도 있습니다.

// rust
enum MyEnum {
    A(String),
    B(String)
}

엥 kotlin 의 enum class 도 값을 가질 수 있는데요? 거기다 enum 에 들어가는 값에 이름도 지정할 수 있잖아요

https://pl.kotl.in/0wRg3aIsr

// kotlin
enum class MyEnum(var value: String) {
    A("야호"),
    B("호호")
}

fun main() {
    val a = MyEnum.A
    a.value += "!!"
    println(a.value) // 야호!!
}

rust enum 은 union 같은 거

근데 kotlin 은 enum 의 각 케이스(variant)별로 다른 값을 넣을 수는 없잖아요. A 에는 String, B 에는 i32 처럼 말이죠.

// rust
enum MyEnum {
    A(String), // A는 익명의 String 타입 값을 지닙니다. 변수이름.0 으로 접근합니다.
    B(i32)     // B는... 생략
}

이거 어디서 많이 안 보셨나요? C 의 union 이랑 비슷합니다.

// c lang
union MyStruct {
    const char* str;
    const int32_t intval;
}

차이점은 런타임에 어떤 케이스(variant)인지 알 수 있다는 것 정도겠네요.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=31dd4e3449d5dfda93de6844975430ff

// rust
enum MyEnum {
    A(String),
    B(i32)
}

fn main() {
    let a = MyEnum::A("야호".into());

    // 요렇게
    if let MyEnum::A(val) = &a { // 아래에서도 쓰려고 "&"a 로 빌려왔습니다.
        println!("{}", val); // 야호
    }

    // 아니면 요렇게
    match a {
        MyEnum::A(val) => println!("{}", val), // 야호
        _ => {}
    }
}

런타임에 케이스를 알고있다는 점에서 C 의 union 보다는 차라리 C++의 std::variant 가 비슷해보이네요. 안 써봐서 모르니 이건 예시는 스킵하구요.

kotlin 에서는 "sealed class" 를 사용해서 상속으로 구현이 가능하긴 합니다. 상세한 건 생략하자구요.

값이 없는 케이스(variant) 도 가능

여기에 더해서 아예 한 케이스에는 값이 있고, 다른 한 케이스에는 값이 없는 것도 가능합니다. 아 물론 Struct 처럼 필드의 이름을 지정하는 것도 되구요.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ebbf6f40baf80d3f881a05cb89abab30

// rust
enum MyEnum 
    StringCase(String), // 익명의 값 String 이 멤버입니다. 변수이름.0 으로 접근합니다.
    EmptyCase,
    FieldCase { value_str: String } 
}

fn main() {
    let string_case = MyEnum::StringCase("야호".into());
    let empty_case = MyEnum::EmptyCase;
    let field_case = MyEnum::FieldCase { value_str: "야호오오".into() };

    if let MyEnum::StringCase(val) = string_case { println!("{}", val); };
    if let MyEnum::EmptyCase = empty_case { println!("비었어요"); };
    if let MyEnum::FieldCase { value_str } = field_case { println!("{}", value_str); };
}

null 이라는 실수를 없애기 위해 enum 을 사용하다

이렇게까지 필요한가? 하실수도 있습니다. 근데 필요합니다. 이게 바로 rust 가 null 을 없앤 방법이거든요.

rust 에서는 null 대신에 enum Option 을 사용합니다. Option::Some 일 때는 값이 있고, Option::None 일 때는 값이 없죠. 너무 자료구조가 뻔해서 struct 자체는 눈 감고도 어떻게 생겼는지 알 수 있어요.

// rust
pub Option<T> {
    Some(T), // 익명의 값 T 가 멤버입니다.
    None
}

null 을 반환해야 할 거 같은 많은 API 에서 대신 Option 을 반환합니다.

요건 iterator 의 next() 함수의 문서로 대체할게요. pub fn next(&mut self) -> Option<Self::Item> 처럼 생겼는데 예시가 있으니 꼭 보세요!

// rust - 위 링크의 문서에서 일부 발췌
let a = [1, 2];
let mut iter = a.iter();

assert_eq!(Some(&1), iter.next());
assert_eq!(Some(&2), iter.next());
assert_eq!(None, iter.next());

비슷한 걸로 Result 도 있습니다.

"Option" 은 C++ 에도 있긴 합니다. std::optional 이라구 있어요.

ADT 라고 부른다고?

이건 아마 함수형 프로그래밍 언어들의 개념에서 가져온 걸겁니다. 어쩌다가 Haskell 코드를 볼 일이 있었는데 똑같은 목적의 Maybe 라는 클래스? 가 있더라구요. 아마 많은 함수형 언어에 있을겁니다. 이런 자료구조를 대수적 자료구조 (Algebraic Data Type) 라고 부란다고 하더라구요. 뭐 아무렴 어때요, 잘 쓰면 그만이죠!

이거 디게 번거롭다... 어떻게 편하게 써요?

프로그램이 죽어도 상관없다면 unwrap() 이나 expect("나 죽었어요 메시지") 를 써보세요 (Option 에도 Result 에도 있음). 아니면 물음표 연산자나 map_err 같이 각 struct 에서 제공해주는 메서드를 사용해보세요.

(요 부분 쓸 시점에 슬슬 지쳐서 대충 쓰다 말았는데 아마 에러 처리 관련해서 찾아보시면 자세한 글들이 나올 겁니다)


아 좀 후련하다
임금님 귀는 당나귀귀

여담인데 최적화를 좋아하는 C++ 여러분들은 nomicon 을 보세요. 으 검색하니 잘 모르는 게 나오는 걸로 봐서 저는 공부를 더 해야 할 거 같네요,,

또 하나 더, enum 이 단순 상수가 아닌 만큼 컨벤션도 전부 대문자로 쓰지는 않는 모양이에요. 코딩 컨벤션 하면 뭐다? 눈치밥 아니겠숨꽈. 제대로된 원칙이 있다면 제일이겠지만...


추가 참고: Rust container cheat sheet (그림이 풍부함), by Raph Levien / 같은 분이 더 나중에 만든 rust cheat sheet (같은 그림이 설명과 함께 있음)

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함