Rust, 从HashMap获取带有枚举的值

huangapple go评论70阅读模式
英文:

Rust, getting values from HashMap with enum

问题

I'm trying to make one HashMap with different types. I don't want to make two different HashMaps for specific data types.

My code is below:

use std::collections::HashMap;

#[derive(Debug)]
enum DataTypes {
    String(String),
    Bool(bool),
}

fn get_hashmap() -> Result<HashMap<String, DataTypes>, ()>{
    let data = HashMap::from([
        ("password".to_string(), DataTypes::String("password".to_string())),
        ("username".to_string(), DataTypes::String("Fun username".to_string())),
        ("is_blocked".to_string(), DataTypes::Bool(true)),
        ("is_confirmed".to_string(), DataTypes::Bool(false)),
    ]);
    Ok(data)
}
fn main() {
    let data = get_hashmap().unwrap();
    let keys = data.keys();
    println!("Keys: {:?}", &keys);
    for key in keys {
        let result: Option<T> = match data.get(key).unwrap() {
            DataTypes::Bool(value) => Some(value),
            DataTypes::String(value) => Some(value),
            _ => panic!("Error!"),
        };
        println!("Result of matching: {:?}", &result);
    }
}

Like you can see I'm trying to match Enums to get their values. But I have some problem of data types.
My solution for this is to wrap the result of matching in the Option<T> struct. But the main problem is not resolved.

So I want to make the result of matching an Option<T> to make it available for unwrap().
But I don't know how I can do that correctly...

I have two questions:

  1. Can I do this better?
  2. How can I wrap let result: Option<T> to a working state?
英文:

I'm trying make one HashMap with different types. I don't want to make two different HashMaps for specific data types.

My code is bellow:

use std::collections::HashMap;

#[derive(Debug)]
enum DataTypes {
    String(String),
    Bool(bool),
}

fn get_hashmap() -&gt; Result&lt;HashMap&lt;String, DataTypes&gt;, ()&gt;{
    let data = HashMap::from([
        (&quot;password&quot;.to_string(), DataTypes::String(&quot;password&quot;.to_string())),
        (&quot;username&quot;.to_string(), DataTypes::String(&quot;Fun username&quot;.to_string())),
        (&quot;is_blocked&quot;.to_string(), DataTypes::Bool(true)),
        (&quot;is_confirmed&quot;.to_string(), DataTypes::Bool(false)),
    ]);
    Ok(data)
}
fn main() {
    let data = get_hashmap().unwrap();
    let keys = data.keys();
    println!(&quot;Keys: {:?}&quot;, &amp;keys);
    for key in keys {
        let result: Option&lt;T&gt; = match data.get(key).unwrap() {
            DataTypes::Bool(value) =&gt; Some(value),
            DataTypes::String(value) =&gt; Some(value),
            _ =&gt; panic!(&quot;Error!&quot;),
        };
        println!(&quot;Result of matching: {:?}&quot;, &amp;result);
    }
}

Like you can see I'm trying to maching Enums to getting their values. But i have some problem of data types.
My solution for this is wrap result of matching to Some struct. But still main problem is not resolved.

So I want to make result of matching in Option<T> class to make available unwrap().
But I don't know how i can do that correctly...

I have two question:

  1. Can i do this better?
  2. How can I wrap let result: Option<T> to working state?

答案1

得分: 1

以下是您要的翻译内容:

Some feedback:

  • 如果您已经处理了所有选项,请不要包括 _ 默认匹配情况。这将隐藏未来的错误。
  • 如果每个成员都只是一个数据类型,请不要将变量命名为 DataTypes。将其命名为 DataType
  • result 必须是一个特定的类型。枚举的整个目的是可以分别处理不同的值,因此将它们组合在 result 类型中是没有意义的。当然,您可以将 result 保留为 DataType 对象并为其实现 Debug/Display,这就是我在重新编写的代码中将如何执行的方式。
  • 尽管您可以首先查询键,然后再在循环中查询值,但这样会相当慢。您可以立即迭代键-值对。这样可以避免许多 unwrap(),使您的代码更不容易出错。
use std::collections::HashMap;

#[derive(Debug)]
enum DataType {
    String(String),
    Bool(bool),
}

fn get_hashmap() -> Result<HashMap<String, DataType>, ()> {
    let data = HashMap::from([
        (
            "password".to_string(),
            DataType::String("password".to_string()),
        ),
        (
            "username".to_string(),
            DataType::String("Fun username".to_string()),
        ),
        ("is_blocked".to_string(), DataType::Bool(true)),
        ("is_confirmed".to_string(), DataType::Bool(false)),
    ]);
    Ok(data)
}
fn main() {
    let data = get_hashmap().unwrap();
    for (key, value) in data {
        println!("{}: {:?}", key, value);

        match value {
            DataType::Bool(value) => {
                println!("\tValue was a bool: {}", value);
                // 如果值是布尔值,执行某些操作
            }
            DataType::String(value) => {
                println!("\tValue was a string: {}", value);
                // 如果值是字符串,执行某些操作
            } /*
               * 不要包括默认情况。这样编译器
               * 会在将来添加枚举条目时提醒您处理它们。
               * 在匹配是穷尽的语言中,添加默认情况只是一种良好的做法。
               */
        };
    }
}
username: String("Fun username")
        Value was a string: Fun username
is_confirmed: Bool(false)
        Value was a bool: false
is_blocked: Bool(true)
        Value was a bool: true
password: String("password")
        Value was a string: password

不过,不必担心,您不需要在使用此枚举的每个地方都使用 match,否则与使用两个单独的哈希映射相比,您不会获得太大的好处。相反,您可以为所有枚举条目定义共享功能,并在其中隐藏 match。就像这样:

use std::collections::HashMap;

#[derive(Debug)]
enum DataType {
    String(String),
    Bool(bool),
}

impl DataType {
    fn do_something(&self) {
        match self {
            DataType::Bool(value) => {
                println!("\tDo something with boolean '{}!'", value);
            }
            DataType::String(value) => {
                println!("\tDo something with string {:?}!", value);
            }
        };
    }
}

fn get_hashmap() -> Result<HashMap<String, DataType>, ()> {
    let data = HashMap::from([
        (
            "password".to_string(),
            DataType::String("password".to_string()),
        ),
        (
            "username".to_string(),
            DataType::String("Fun username".to_string()),
        ),
        ("is_blocked".to_string(), DataType::Bool(true)),
        ("is_confirmed".to_string(), DataType::Bool(false)),
    ]);
    Ok(data)
}

fn main() {
    let data = get_hashmap().unwrap();
    for (key, value) in data {
        println!("{}: {:?}", key, value);
        value.do_something();
    }
}
is_confirmed: Bool(false)
        Do something with boolean 'false'!
password: String("password")
        Do something with string "password"!
is_blocked: Bool(true)
        Do something with boolean 'true'!
username: String("Fun username")
        Do something with string "Fun username"!

如果您的目标是向结构体添加序列化/反序列化(正如您在这里手动实现的),让我向您提示 serde,它已经为大多数序列化工作提供了免费的解决方案。

就像在这个例子中一样(这可能是您的结构看起来如何的例子),将您的结构体序列化为 JSON 并从 JSON 反序列化:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct User {
    username: String,
    password: String,
    is_blocked: bool,
    is_confirmed: bool,
}

fn main() {
    let user = User {
        username: "Fun username".to_string(),
        password: "password".to_string(),
        is_blocked: true,
        is_confirmed: false,
    };

    let user_serialized = serde_json::to_string(&user).unwrap();
    println!("Serialized: {}", user_serialized);

    let user_deserialized: User = serde_json::from_str(&user_serialized).unwrap();
    println!("Name: {}", user_deserialized.username);
}
Serialized: {"username":"Fun username","password":"password","is_blocked":true,"is_confirmed":false}
Name: Fun username
英文:

Some feedback:

  • Don't include a _ default match case if you already handle all the options. It will hide future errors.
  • Don't name a variable DataTypes if every member is only a single datatype. Name it DataType.
  • result has to be a specific type. The whole point of the enum is that you can handle the different values separately, so combining them in a result type is pointless. Although you of course can keep result a DataType object and implement Debug/Display for it, which is how I will do it in my reworked code.
  • While you can query the key first and then again query the value in the loop, this is quite slow. You can iterate over key-value pairs right away. That way you avoid a lot of unwrap()s, which makes your code a lot less error-prone.
use std::collections::HashMap;

#[derive(Debug)]
enum DataType {
    String(String),
    Bool(bool),
}

fn get_hashmap() -&gt; Result&lt;HashMap&lt;String, DataType&gt;, ()&gt; {
    let data = HashMap::from([
        (
            &quot;password&quot;.to_string(),
            DataType::String(&quot;password&quot;.to_string()),
        ),
        (
            &quot;username&quot;.to_string(),
            DataType::String(&quot;Fun username&quot;.to_string()),
        ),
        (&quot;is_blocked&quot;.to_string(), DataType::Bool(true)),
        (&quot;is_confirmed&quot;.to_string(), DataType::Bool(false)),
    ]);
    Ok(data)
}
fn main() {
    let data = get_hashmap().unwrap();
    for (key, value) in data {
        println!(&quot;{}: {:?}&quot;, key, value);

        match value {
            DataType::Bool(value) =&gt; {
                println!(&quot;\tValue was a bool: {}&quot;, value);
                // do something if the value is a bool
            }
            DataType::String(value) =&gt; {
                println!(&quot;\tValue was a string: {}&quot;, value);
                // do something if the value is a string,
            } /*
               * Don&#39;t include a default case. That way the compiler
               * will remind you to handle additional enum entries if
               * you add them in the future.
               * Adding a default case is only a good practice in languages
               * where matching is not exhaustive.
               */
        };
    }
}
username: String(&quot;Fun username&quot;)
        Value was a string: Fun username
is_confirmed: Bool(false)
        Value was a bool: false
is_blocked: Bool(true)
        Value was a bool: true
password: String(&quot;password&quot;)
        Value was a string: password

Don't worry though, you don't need to use match everywhere you use this enum, otherwise you wouldn't win much compared to two separate hashmaps. You can, instead, define shared functionality for all enum entries, and hide the match inside of it. Like this:

use std::collections::HashMap;

#[derive(Debug)]
enum DataType {
    String(String),
    Bool(bool),
}

impl DataType {
    fn do_something(&amp;self) {
        match self {
            DataType::Bool(value) =&gt; {
                println!(&quot;\tDo something with boolean &#39;{}&#39;!&quot;, value);
            }
            DataType::String(value) =&gt; {
                println!(&quot;\tDo something with string {:?}!&quot;, value);
            }
        };
    }
}

fn get_hashmap() -&gt; Result&lt;HashMap&lt;String, DataType&gt;, ()&gt; {
    let data = HashMap::from([
        (
            &quot;password&quot;.to_string(),
            DataType::String(&quot;password&quot;.to_string()),
        ),
        (
            &quot;username&quot;.to_string(),
            DataType::String(&quot;Fun username&quot;.to_string()),
        ),
        (&quot;is_blocked&quot;.to_string(), DataType::Bool(true)),
        (&quot;is_confirmed&quot;.to_string(), DataType::Bool(false)),
    ]);
    Ok(data)
}

fn main() {
    let data = get_hashmap().unwrap();
    for (key, value) in data {
        println!(&quot;{}: {:?}&quot;, key, value);
        value.do_something();
    }
}
is_confirmed: Bool(false)
        Do something with boolean &#39;false&#39;!
password: String(&quot;password&quot;)
        Do something with string &quot;password&quot;!
is_blocked: Bool(true)
        Do something with boolean &#39;true&#39;!
username: String(&quot;Fun username&quot;)
        Do something with string &quot;Fun username&quot;!

If your goal is to add serialization/deserialization to your struct (as you seem to implement manually here), let me hint you towards serde, which already takes care of the majority of serialization for free.

Like in this example (which may or may not be how your struct looks like) that serializes your struct to and from JSON:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct User {
    username: String,
    password: String,
    is_blocked: bool,
    is_confirmed: bool,
}

fn main() {
    let user = User {
        username: &quot;Fun username&quot;.to_string(),
        password: &quot;password&quot;.to_string(),
        is_blocked: true,
        is_confirmed: false,
    };

    let user_serialized = serde_json::to_string(&amp;user).unwrap();
    println!(&quot;Serialized: {}&quot;, user_serialized);

    let user_deserialized: User = serde_json::from_str(&amp;user_serialized).unwrap();
    println!(&quot;Name: {}&quot;, user_deserialized.username);
}
Serialized: {&quot;username&quot;:&quot;Fun username&quot;,&quot;password&quot;:&quot;password&quot;,&quot;is_blocked&quot;:true,&quot;is_confirmed&quot;:false}
Name: Fun username

答案2

得分: 0

由于Finomnis的帮助,我找到了我所寻找的方法。
这是我的临时解决方案。

use std::collections::HashMap;

#[derive(Debug)]
enum DataType {
    String(String),
    Bool(bool),
}

impl DataType {
    fn get_bool(&self) -> bool {
        let result = match self {
            DataType::Bool(value) => value.to_owned(),
            _ => panic!("Something"),
        };
        result
    }

    fn get_string(&self) -> String {
        let result = match self {
            DataType::String(value) => value.to_owned(),
            _ => panic!("Something"),
        };
        result
    }
}

fn get_hashmap() -> Result<HashMap<String, DataType>, ()>{
    let data = HashMap::from([
        ("password".to_string(), DataType::String("password".to_string())),
        ("username".to_string(), DataType::String("Fun username".to_string())),
        ("is_blocked".to_string(), DataType::Bool(true)),
        ("is_confirmed".to_string(), DataType::Bool(false)),
    ]);
    Ok(data)
}

fn main() {
    // 现在我们可以简单地从哈希映射中获取值
    let mut my_hash_map = get_hashmap().unwrap();
    let username = my_hash_map.remove("username").unwrap().get_string();
    let is_blocked = my_hash_map.remove("is_blocked").unwrap().get_bool();

    // 现在我们清晰地知道了用户名和是否被阻止。
    println!("{}", username);
    println!("{}", is_blocked);
}

我认为当我们想要使用数据库时,这可能会有所帮助。当我们不想序列化所有用户结构时。

也许这可以做得更好。
再次感谢所有人的大力帮助! Rust, 从HashMap获取带有枚举的值

英文:

Thanks to Finomnis I found way what I'm looking for.
This is my scratch solution.

use std::collections::HashMap;

#[derive(Debug)]
enum DataType {
    String(String),
    Bool(bool),
}

impl DataType {
    fn get_bool(&amp;self) -&gt; bool {
        let result = match self {
            DataType::Bool(value) =&gt; value.to_owned(),
            _ =&gt; panic!(&quot;Something&quot;),
        };
        result
    }

    fn get_string(&amp;self) -&gt; String {
        let result = match self {
            DataType::String(value) =&gt; value.to_owned(),
            _ =&gt; panic!(&quot;Something&quot;),
        };
        result
    }
}

fn get_hashmap() -&gt; Result&lt;HashMap&lt;String, DataType&gt;, ()&gt;{
    let data = HashMap::from([
        (&quot;password&quot;.to_string(), DataType::String(&quot;password&quot;.to_string())),
        (&quot;username&quot;.to_string(), DataType::String(&quot;Fun username&quot;.to_string())),
        (&quot;is_blocked&quot;.to_string(), DataType::Bool(true)),
        (&quot;is_confirmed&quot;.to_string(), DataType::Bool(false)),
    ]);
    Ok(data)
}

fn main() {
    // now we can simply get values from hashmap
    let mut my_hash_map = get_hashmap().unwrap();
    let username = my_hash_map.remove(&quot;username&quot;).unwrap().get_string();
    let is_blocked = my_hash_map.remove(&quot;is_blocked&quot;).unwrap().get_bool();

    // Now we have clear value of username and is_blocked.
    println!(&quot;{}&quot;, username);
    println!(&quot;{}&quot;, is_blocked);
}

I think this can be helpful when we are want using Databases. When we don't want serialize all User structure.

Probably this can be done much better.
Again thanks all for BIG help! Rust, 从HashMap获取带有枚举的值

huangapple
  • 本文由 发表于 2023年2月19日 19:37:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/75499855.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定