ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Rust 강좌 8 - anymap
    Rust 2024. 10. 8. 22:20

    Rust 강좌 8 - anymap

    이번 글에서는 Rust의 anymap 크레이트에 대해 다룰 건데요.

     

    이 크레이트는 Rust-http와 teepee로 유명한 Chris Morgan이 만든 것인데요.

     

    이 크레이트는 약간 특이하고 흥미로운 컨테이너인 AnyMap 타입을 제공해요.

     

    AnyMap 타입은 일반적인 맵과는 다릅니다.

     

    예를 들어, Rust의 HashMap은 K(키의 타입)와 V(맵에 저장된 값의 타입)에 의해 매개변수화된 제네릭 타입이에요.

     

    (여기에는 해시 함수(hasher) 매개변수도 있지만, 이는 여기에서는 관련이 없어요.) 반면, AnyMap 자체는 제네릭 타입이 아니에요.

     

    내부적으로 HashMap을 사용하지만, 그 사실을 알 필요는 없어요.

     

    개념적으로, AnyMap은 타입에서 값으로 매핑합니다.

     

    즉, 모든 타입에 대해 매핑에 포함될 수 있는 값은 최대 하나뿐이에요.

     

    "왜 각 타입당 하나의 값만을 보관하는 것이 필요할까?"라고 물어볼 수 있을 텐데요.

     

    많은 프로그램은 (보통) 문자열에서 임의의 값으로의 맵을 사용해요.

     

    예를 들어, 설정 데이터나 프로세스 환경을 저장할 때 그렇죠.

     

    Chris의 말을 인용해볼게요:

    "일반적으로 map[string]interface{}와 비슷한 형태로, 임의의 문자열로 접근하게 되는데요. 이는 충돌이 일어날 수 있고, 타입 단언(type assertions)이 다소 번거롭고 매우 조심스럽게 사용해야 해요. (개인적으로, 이는 일이 터지기를 바라는 것이라고 생각해요.) Go와 같은 제네릭이 없는 언어에서는, 이게 최선이죠; 제네릭 없이는 안전하게 만들 수 없어요."

     

    AnyMap을 새로운 타입(newtype) 관용구와 함께 사용하면 강력하게 타입이 지정된 구성(configuration) 홀더를 만들 수 있는데요.

    extern crate anymap;
    
    use std::net::Ipv4Addr;
    use anymap::AnyMap;
    
    #[derive(Debug)]
    enum HostAddress {
        DomainName(String),
        Ip(Ipv4Addr),
    }
    
    #[derive(Debug)]
    struct Port(u32);
    
    #[derive(Debug)]
    struct ConnectionLimit(u32);
    let mut config = AnyMap::new();
    config.insert(HostAddress::DomainName("siciarz.net".to_string()));
    config.insert(Port(666));
    config.insert(ConnectionLimit(32));
    println!("{:?}", config.get::<HostAddress>());
    println!("{:?}", config.get::<Port>());
    assert!(config.get::<String>().is_none());
    assert!(config.get::<u32>().is_none());

     

    출력 결과:

    $ cargo run
    Some(DomainName("siciarz.net"))
    Some(Port(666))

     

    여기서 PortConnectionLimit 타입은 기본 정수에 대한 추상화인데요(런타임에서 오버헤드가 없어요!).

     

    이 두 타입을 혼합하는 것도 불가능한데요.

     

    이들은 전혀 다른 타입이기 때문이에요(즉, u32의 별칭이 아니죠).

     

    따라서 AnyMap에서 별개의 항목으로 사용할 수 있다는 뜻이죠.

     

    위의 예에서 보여주듯이 맞는 말이에요.

     

    또한, 새로운 타입으로 감싼 값을 삽입하면 원래 타입이 매핑에 나타나지 않는다는 점도 주목할 만해요(이건 당연한 것 같네요).

     

    AnyMap에 이미 존재하는 타입의 다른 값을 삽입하면 이전 값이 덮어쓰여지는데요.

     

    다른 열거형 변형이더라도 마찬가지예요.

     

    열거형 변형은 하나의 타입 아래 그룹화된 값이므로, AnyMap에서는 타입에서 값으로의 매핑으로 생각해야 한다는 점을 기억해야 해요.

    config.insert(HostAddress::Ip(Ipv4Addr::new(127, 0, 0, 1)));
    println!("{:?}", config.get::<HostAddress>());

     

    출력 결과:

    $ cargo run
    Some(Ip(127.0.0.1))

     

    제네릭 타입은 각 타입 매개변수에 대해 다르게 간주되므로, 예를 들어 모든 Option 타입은 AnyMap에 별도의 항목으로 등록됩니다.

    if !config.contains::<Option<f32>>() {
        println!("There's no optional 32-bit float in the configuration...");
    }
    let dummy: Option<f32> = None;
    config.insert(dummy);
    if config.contains::<Option<f32>>() {
        println!("There's an optional 32-bit float in the configuration...");
    }
    if !config.contains::<Option<f64>>() {
        println!("...but not an optional 64-bit float.");
    }

     

Designed by Tistory.