What is the best way to convert a Vec<(String, String)> into a struct

I'm currently writing a web application that uses diesel for database interaction. I have one table for global settings with two columns, one for the setting and the other the value. This gets serialized by diesel into a Vec<(String, String)>.
The problem is that I somehow have to convert the Vec<(String, String)> into a single Settings struct. Currently this is done by converting the vector into an iterator, convert the iterator into a hashmap and use the hashmap to construct Settings:

use std::collections::HashMap;


fn main() {
    // Settings are queried from the database here
    let settings: Vec<(String, String)> = vec![("setting1".to_string(), "value1".to_string()), ("setting3".to_string(), "value3".to_string())];
    
    let mut settings: HashMap<String, String> = settings.into_iter().collect();
    
    let settings = Settings {
        setting1: settings.remove("setting1").unwrap_or_else(|| "defaultvalue1".to_string()),
        setting2: settings.remove("setting2").unwrap_or_else(|| "defaultvalue2".to_string()),
        setting3: settings.remove("setting3").unwrap_or_else(|| "defaultvalue3".to_string()),
    };
    
    assert_eq!(
        settings,
        Settings {
            setting1: "value1".to_string(),
            setting2: "defaultvalue2".to_string(),
            setting3: "value3".to_string(),
        }
    );
}

#[derive(Debug ,PartialEq)]
struct Settings {
    setting1: String,
    setting2: String,
    setting3: String,
}

However, this feels like it isn't the best way of doing this. Are there better/more efficient ways of doing this?

EDIT:
I do know that the default hasher of HashMap is slow, I just used it in this example for the sake of simplicity.

What you've done works fine, and for a small table it's gonna be extremly fast. However, you could avoid allocating an HashMap and then looking into by directly iterating over yhe Vec:

impl Settings {
    fn from_pairs(pairs: Vec<(String, String)>) -> Self {
        let mut s = Settings {
            setting1: "defaultvalue1".into(),
            setting2: "defaultvalue2".into(),
            setting3: "defaultvalue3".into(),
        };

        for (k, v) in pairs {
            match k.as_str() {
                "setting1" => s.setting1 = v,
                "setting2" => s.setting2 = v,
                "setting3" => s.setting3 = v,
                _ => {}
            }
        }

        s
    }
}

If you have a small number of settings, you should privilegiate this method, because it's faster and avoid unnecessary allocations.

However, if you have a huge number of settings and/or your settings are not fixed, the HashMap will probably be the best way. If you keep the HashMap, you should consider changing the .remove("key").unwrap_or_else(...) by .get("key").cloned().unwrap("default".into()), to avoid the HashMap to rehash everything.

2 Likes

Thanks for your help! I didn't know about the side effects of .remove("key") since my knowledge about hashmaps is quite superficial. I still don't like the need to allocate those default strings though, but I suppose there's no better option.

You can use Cow<'static, str> if you want to avoid allocating hardcoded strings.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.