1

Edit

As it seemms from the suggested solution, What I'm trying to achieve seems impossible/Not the correct way, therefore - I'll explain the end goal here:

I am parsing the values for Foo from a YAML file using serde, and I would like to let the user get one of those stored values from the yaml at a time, this is why I wanted to store an iterator in my struct


I have two struct similar to the following:

struct Bar {
    name: String,
    id: u32
}

struct Foo {
    my_map: HashMap<String, Bar>
}

In my Foo struct, I wish to store an iterator to my HashMap, so a user can borrow values from my map on demand. Theoretically, the full Foo class would look something like:

struct Foo {
    my_map: HashMap<String, Bar>,
    my_map_iter: HashMap<String, Bar>::iterator
}

impl Foo {
    fn get_pair(&self) -> Option<(String, Bar)> {
        // impl...
    }
}

But I can't seem to pull it off and create such a variable, no matter what I try (Various compilation errors which seems like I'm just trying to do that wrong).

I would be glad if someone can point me to the correct way to achieve that and if there is a better way to achieve what I'm trying to do - I would like to know that.

Thank you!

Or Y
  • 2,088
  • 3
  • 16
  • 4
    This is an extension of [Why can't I store a value and a reference to that value in the same struct?](https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct) – kmdreko Feb 24 '21 at 15:43
  • I'm also not sure that it makes any sense in and of itself: "a user can borrow values from my map on demand" what happens once the iterator is exhausted? – Masklinn Feb 24 '21 at 15:51
  • @Masklinn My initial intention was to just create a new iterator and start over - I parse values from a YAML with serde and would like to let the user of the class use those values on demand – Or Y Feb 24 '21 at 15:56
  • I've retracted my duplicate vote since you're now looking for a specific workaround. However, can you clarify the desired patterns? Why can your `Foo` class not provide an iterator to use at the user's leisure? – kmdreko Feb 24 '21 at 16:12
  • @kmdreko In my actual usecase, Bar is a server configuration that I'll need to use in some other place in the code later on to try and connect to that server. My YAML file contains many settings like this and occasionally I want to get a new one to use. My Foo struct is where I parsed my YAML to and store it for later use. The YAML is not too big so I'm also fine with keeping it all in the memory. – Or Y Feb 24 '21 at 16:17
  • so, `Foo` contains a set of server configurations `Bar`, and one is supposed to be current? *"I want to get a new one to use"* - like for a failover system? – kmdreko Feb 24 '21 at 16:22
  • @kmdreko A failover system would suit that as well, What I am working on has a few servers which configurations are stored in `Foo`s in my YAML, and the flow of my program is that I sample one of those Servers and when I'm done, I'm moving to the next one in my YAML – Or Y Feb 24 '21 at 16:24

2 Answers2

2

I am parsing the values for Foo from a YAML file using serde

When you parse them you should put the values in a Vec instead of a HashMap.

I imagine the values you have also have names which is why you thought a HashMap would be good. You could instead store them like so:

let parsed = vec![]

for _ in 0..n_to_parse {
    // first item of the tuple is the name second is the value
    let key_value = ("Get from", "serde");
    parsed.push(key_value);
}

then once you stored it like so it will be easy to get the pairs from it by keeping track of the current index:

struct ParsedHolder {
    parsed: Vec<(String, String)>,
    current_idx: usize,
}

impl ParsedHolder {
    fn new(parsed: Vec<(String, String)>) -> Self {
        ParsedHolder {
            parsed,
            current_idx: 0,
        }
    }

    fn get_pair(&mut self) -> Option<&(String, String)> {
        if let Some(pair) = self.parsed.get(self.current_idx) {
            self.current_idx += 1;
            Some(pair)

        } else {
            self.current_idx = 0;
            None
        }
    }
}

Now this could be further improved upon by using VecDeque which will allow you to efficiently take out the first element of parsed. Which will make it easy to not use clone. But this way you will be only able to go through all the parsed values once which I think is actually what you want in your use case.

But I'll let you implement VecDeque

Hadus
  • 1,551
  • 11
  • 22
  • 1
    If it is a pain to collect it straight into a vector you can use `hashmap::into_iter()::collect::Vec<_, _>()` – Hadus Feb 24 '21 at 16:30
  • 1
    Thank you again for another solution! The reason I've went with a HashMap was because this YAML could be updated (I also sample that YAML once in a few minutes) and then I might need to update one of the fields, which I could have accessed in O(1) because I would know what ID has changed. But as the YAML shouldn't be that big and because the HashMap giving me a hard time actually using the parsed values as I need them, Switching to a vector might be a better idea indeed, and then I'll just reparse my YAML. – Or Y Feb 24 '21 at 16:30
  • Would such an update to the values happen while you are still getting pairs from it? – Hadus Feb 24 '21 at 16:33
  • 1
    @OrY if the YAML can be updated, you'll have to parse the whole thing regardless so it would probably be less work to just swap the whole thing out rather than trying to see what changed and only swapping those. – kmdreko Feb 24 '21 at 16:33
  • 1
    @kmdreko You are indeed correct, So I'll try to change the YAML parsing to a vector instead of a HashMap – Or Y Feb 24 '21 at 16:35
1

The reason why this is a hard is that unless we make sure the HashMap isn't mutated while we iterate we could get into some trouble. To make sure the HashMap is immutable until the iterator lives:

use std::collections::HashMap;
use std::collections::hash_map::Iter;

struct Foo<'a> {
    my_map: &'a HashMap<u8, u8>,
    iterator: Iter<'a, u8, u8>,
}

fn main() {
    let my_map = HashMap::new();
    let iterator = my_map.iter();

    let f = Foo {
        my_map: &my_map,
        iterator: iterator,
    };
}

If you can make sure or know that the HashMap won't have new keys or keys removed from it (editing values with existing keys is fine) then you can do this:

struct Foo {
    my_map: HashMap<String, String>,
    current_idx: usize,
}

impl Foo {
    fn new(my_map: HashMap<String, String>) -> Self {
        Foo {
            my_map,
            current_idx: 0,
        }
    }

    fn get_pair(&mut self) -> Option<(&String, &String)> {
        if let Some(pair) = self.my_map.iter().skip(self.current_idx).next() {
            self.current_idx += 1;
            Some(pair)

        } else {
            self.current_idx = 0;
            None
        }
    }

    fn get_pair_cloned(&mut self) -> Option<(String, String)> {
        if let Some(pair) = self.my_map.iter().skip(self.current_idx).next() {
            self.current_idx += 1;
            Some((pair.0.clone(), pair.1.clone()))

        } else {
            self.current_idx = 0;
            None
        }
    }
}

This is fairly inefficient though because we need to iterate though the keys to find the next key each time.

Hadus
  • 1,551
  • 11
  • 22
  • First of all thank you! While it seems to work and solve my solution, as I wish to improve the efficiency of that and as I'm worried that later on I might want to modify one of the values in the HashMap, I was wondering if there is a better way to implement a method to my struct that returns a key value pair from my HashMap when requested. (Ideally I would use IndexMap but I had hard time parsing a YAML into it, as it didn't work as natively with serde as HashMap did). – Or Y Feb 24 '21 at 16:22