Rust, получение значений из HashMap с перечислением

Я пытаюсь сделать один HashMap с разными типами. Я не хочу создавать два разных HashMaps для определенных типов данных.

Мой код ниже:

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);
    }
}

Как вы можете видеть, я пытаюсь обработать Enums, чтобы получить их значения. Но у меня есть некоторые проблемы с типами данных. Мое решение для этого - обернуть результат сопоставления с некоторой структурой. Но до сих пор основная проблема не решена.

Итак, я хочу сделать результат сопоставления в классе Option, чтобы сделать доступным unwrap(). Но я не знаю, как это сделать правильно...

У меня два вопроса:

  1. Могу ли я сделать это лучше?
  2. Как я могу обернуть let result: Option в рабочее состояние?

Что ты хочешь сделать с result? Они разных типов.

Chayim Friedman 19.02.2023 12:45

Я хочу отправить эти значения в базу данных. Перед отправкой данных в базу данных я хочу сериализовать результат в структуру пользователя.

RfDzDeveloper 19.02.2023 12:47

Тогда почему вы не можете сериализовать их внутри match?

Chayim Friedman 19.02.2023 12:48

Или просто сериализуйте перечисление напрямую (для serde, #[derive(Serialize)] #[serde(untagged)] в перечислении).

Chayim Friedman 19.02.2023 12:49

Хороший вопрос, пока я не знаю, как это сделать, но я думаю, что это хорошая идея.

RfDzDeveloper 19.02.2023 13:05
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
5
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Некоторые отзывы:

  • Не включайте случай соответствия _ по умолчанию, если вы уже обрабатываете все параметры. Это скроет будущие ошибки.
  • Не называйте переменную 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);
                // do something if the value is a bool
            }
            DataType::String(value) => {
                println!("\tValue was a string: {}", value);
                // do something if the value is a string,
            } /*
               * Don'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("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 и обратно:

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

Спасибо за хороший отзыв. Это действительно полезно :) Мне нужно согласиться с вами во всех пунктах. Объединение всех типов данных в один результат имеет одну вескую причину. Мне нужно использовать его для отправки этого вывода в базу данных. У меня гораздо более сложная структура пользователя со свойствами Option<T>. Я хочу избежать сопоставления каждой записи в HashMap и получить значение из Enum. Но знайте, я вижу, что более простой метод используется для двух HashMaps с разными типами данных. Гораздо менее сложно.

RfDzDeveloper 19.02.2023 15:57

Этот трюк с распаковкой хэш-карты в цикле for просто великолепен! Спасибо :)

RfDzDeveloper 19.02.2023 16:04

@RfDzDeveloper Я не совсем согласен, я думаю, что использование перечисления нормально. Вы можете реализовать совместное поведение, скрывая операторы соответствия в реализации. Смотрите добавленный раздел в моем ответе.

Finomnis 19.02.2023 16:18

Я думаю, что это отличный ответ! Теперь я ясно понимаю, что произошло и что я могу с этим сделать. Большое, большое спасибо за ваше время и объяснение мне этой концепции.

RfDzDeveloper 19.02.2023 16:26

@RfDzDeveloper Но ChayimFriedman прав, если ваша проблема касается сериализации/десериализации, большая часть работы в Rust уже сделана, если вы используете serde. Хотя выполнение этого вручную, вероятно, также является хорошим упражнением для улучшения ваших навыков программирования.

Finomnis 19.02.2023 16:32

@RfDzDeveloper Добавлен третий раздел, чтобы продемонстрировать, как serde может работать с вашей структурой.

Finomnis 19.02.2023 16:38

Благодаря 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() {
    // now we can simply get values from hashmap
    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();

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

Я думаю, что это может быть полезно, когда мы хотим использовать базы данных. Когда мы не хотим сериализовать всю структуру пользователя.

Вероятно, это можно сделать намного лучше. Еще раз спасибо всем за БОЛЬШУЮ помощь! :)

Другие вопросы по теме