简体   繁体   English

如何从枚举返回内部类型变体引用而无需到处都有泛型?

[英]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:以下是 2 个无法编译的示例:

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: ,如果我可以使用带有结构的Box ,它会起作用:

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 .我知道键和值类型的大小相同,因此内存中的表示形式将是相似的,但是 rust 不会让您在不使用unsafe 的情况下在这两种类型之间进行转换。

The following example does not use unsafe.下面的例子没有使用 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)                                    
    }                                                               
}                                                                   

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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