简体   繁体   中英

How to return an inner type variant reference from an enum without having generics all over the place?

Here are 2 examples that do not compile:

type Common<K, V> = HashMap<K, V>;
type Variant1 = Common<u32, u64>;
type Variant2 = Common<i32, i64>;

enum Stuff {
    V1(Variant1),
    V2(Variant2),
}

impl Stuff {
    fn new(variant1: bool) -> Stuff {
        if variant1 {
            Stuff::V1(Variant1::new())
        } else {
            Stuff::V2(Variant2::new())
        }
    }

    // Example 1
    fn get<K, V>(&self) -> &Common<K, V> {
        match self {
            Stuff::V1(x) => x,
            Stuff::V2(x) => x,
        }
    }

    // Example 2
    fn get_key<K, V>(&self, key: K) -> Option<&V> {
        match self {
            Stuff::V1(x) => x.get(key),
            Stuff::V1(x) => x.get(key),
        }
    }
}

playground

error[E0308]: mismatched types
  --> src/main.rs:23:29
   |
21 |     fn get<K, V>(&self) -> &Common<K, V> {
   |            - this type parameter
22 |         match self {
23 |             Stuff::V1(x) => x,
   |                             ^ expected type parameter `K`, found `u32`
   |
   = note: expected reference `&std::collections::HashMap<K, V>`
              found reference `&std::collections::HashMap<u32, u64>`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error[E0308]: mismatched types
  --> src/main.rs:30:35
   |
28 |     fn get_key<K, V>(&self, key: K) -> &V {
   |                - this type parameter
29 |         match self {
30 |             Stuff::V1(x) => x.get(key),
   |                                   ^^^ expected `&u32`, found type parameter `K`
   |
   = note:   expected reference `&u32`
           found type parameter `K`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error[E0308]: mismatched types
  --> src/main.rs:30:29
   |
30 |             Stuff::V1(x) => x.get(key),
   |                             ^^^^^^^^^^ expected `&V`, found enum `std::option::Option`
   |
   = note: expected reference `&V`
                   found enum `std::option::Option<&u64>`

error[E0308]: mismatched types
  --> src/main.rs:31:35
   |
28 |     fn get_key<K, V>(&self, key: K) -> &V {
   |                - this type parameter
...
31 |             Stuff::V1(x) => x.get(key),
   |                                   ^^^ expected `&u32`, found type parameter `K`
   |
   = note:   expected reference `&u32`
           found type parameter `K`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

I would like to have an alternative that would allow me to manipulate (and get things from) the inner type of the variant without having generics all over the place.

As suggested in How can I avoid a ripple effect from changing a concrete struct to generic? , if I can use a Box with a struct instead, it works:

use std::collections::HashMap;

trait Common<K, V> {
    fn get(&self, key: &K) -> Option<&V>;
}

struct Variant1(HashMap<u32, u64>);
struct Variant2(HashMap<i32, i64>);

impl Common<u32, u64> for Variant1 {
    fn get(&self, key: &u32) -> Option<&u64> {
        self.get(key)
    }
}
impl Common<i32, i64> for Variant2 {
    fn get(&self, key: &i32) -> Option<&i64> {
        self.get(key)
    }
}

struct Stuff<K, V>(Box<dyn Common<K, V>>);

impl<K, V> Stuff<K, V> {
    fn new(variant1: bool) -> Stuff<K, V> {
        if variant1 {
            Stuff(Box::new(Variant1(HashMap::new())))
        } else {
            Stuff(Box::new(Variant2(HashMap::new())))
        }
    }
}

impl<K, V> Common<K, V> for Stuff<K, V> {
    fn get(&self, key: &K) -> Option<&V> {
        self.0.get(key)
    }
}

fn main() {
    let stuff1 = Stuff::new(true);
    let r1 = stuff1.get(&42);
    let stuff2 = Stuff::new(true);
    let r2 = stuff2.get(&42);
}

playground

However, because this is no longer an enum, I cannot create the variant under one enum/struct anymore (the code above doesn't compile).

On one hand, I want to be able to create one single struct/enum that holds multiple complicated types (enum), but on the other hand I want to be able to get/access to the underlying object. I can't find a way to do both things.

HashMap<u32, u64> != HashMap<i32, i64>

I know the key and value types are the same size, so the in memory representation will be similar, but rust won't let you cast back and fourth between the two types without using unsafe .

The following example does not use unsafe.

You should note that the examples in this thread are not logically sound because casting between signed and unsigned integers yields incorrect results.

use std::collections::HashMap;                       

struct Stuff {                                       
    map: HashMap<[u8; 4], [u8; 8]>,                  
}                                                    

impl Stuff {                                         
    fn new() -> Stuff {                              
        Stuff {                                      
            map: HashMap::new(),                     
        }                                            
    }                                                

    fn get_u32_u64(&self, key: u32) -> Option<u64> { 
        self.map                                     
            .get(&key.to_ne_bytes())                 
            .cloned()                                
            .map(u64::from_ne_bytes)                 
    }                                                

    fn get_i32_u64(&self, key: i32) -> Option<u64> { 
        self.map                                     
            .get(&key.to_ne_bytes())                 
            .cloned()                                
            .map(u64::from_ne_bytes)                 
    }                                                

    fn get_u32_i64(&self, key: u32) -> Option<i64> { 
        self.map                                     
            .get(&key.to_ne_bytes())                 
            .cloned()                                
            .map(i64::from_ne_bytes)                 
    }                                                

    fn get_i32_i64(&self, key: i32) -> Option<i64> { 
        self.map                                     
            .get(&key.to_ne_bytes())                 
            .cloned()                                
            .map(i64::from_ne_bytes)                 
    }                                                
}                                                    

Here is another alternative using traits.

use std::collections::HashMap;                                      

struct Stuff {                                                      
    map: HashMap<[u8; 4], [u8; 8]>,                                 
}                                                                   

trait StuffKey {                                                    
    fn key(self) -> [u8; 4];                                        
}                                                                   

trait StuffValue {                                                  
    fn val(val: [u8; 8]) -> Self;                                   
}                                                                   

impl Stuff {                                                        
    fn new() -> Stuff {                                             
        Stuff {                                                     
            map: HashMap::new(),                                    
        }                                                           
    }                                                               

    fn get<K: StuffKey, V: StuffValue>(&self, key: K) -> Option<V> {
        self.map.get(&key.key()).cloned().map(StuffValue::val)      
    }                                                               
}                                                                   

impl StuffKey for i32 {                                             
    fn key(self) -> [u8; 4] {                                       
        self.to_ne_bytes()                                          
    }                                                               
}                                                                   

impl StuffKey for u32 {                                             
    fn key(self) -> [u8; 4] {                                       
        self.to_ne_bytes()                                          
    }                                                               
}                                                                   

impl StuffValue for i64 {                                           
    fn val(val: [u8; 8]) -> Self {                                  
        Self::from_ne_bytes(val)                                    
    }                                                               
}                                                                   

impl StuffValue for u64 {                                           
    fn val(val: [u8; 8]) -> Self {                                  
        Self::from_ne_bytes(val)                                    
    }                                                               
}                                                                   

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM