Handling Vec with data and a state Vec that changes together with data

It's hard to explain but I'm trying to make a struct that contains a Vec which holds data and another Vec holding some state for each element in the data Vec.

struct DataAndState {
    data: Vec<Data>,
    data_state: Vec<State>,
}

I've made methods on the struct such as push, remove which will modify data_state along changing the data and this was working fine, but I need to give another function mutable access to the data Vec directly, which the function can change data's length which will desync the relation between data and data_state with the way I handle this, and I need some help on how to solve this issue.

Your question, as I interpret it, isn't specific to Rust. SoA layouts are generally more trouble than AofS layouts to work with because you need to make sure to keep the indices in sync (as you point out). Typically SoA is chosen for performance (i.e. cache locality) reasons. (AoS and SoA - Wikipedia)

Perhaps you can explain a little more about what you need to do and why you've made the choices you've made. For example, why do you give the other function access to the internal data member rather than having it call through your accessor APIs that keep both in sync?

2 Likes

The data Vec itself is stored inside another struct which I store in my own struct with the data_state looking like this.

struct OtherStruct {
    // some fields
    data: Vec<Data>,
}

struct DataAndState {
    other_struct: OtherStruct,
    data_state: Vec<State>,
}

The OtherStruct is from another crate and since I need to store it somewhere in my project, I made my struct which also has the states for other_struct's data Vec.

The other function takes the struct itself as an argument, and the function is in another crate which I can't modify.

What about just giving it a &mut [Data] to the vector data instead of an &mut Vec<Data>? Then it can't be resized.

1 Like

The function will usually resize the vec which is why it's a problem for me.

You could go for something like this:

struct DataAndState {
    data: Vec<Data>,
    data_state: Vec<State>,
}

impl DataAndState {
    fn with_data<F, T>(&mut self, func: F) -> T
    where
        F: FnOnce(&mut Vec<Data>) -> T,
    {
        let out = func(&mut self.data);
        // fix up data_state here
        out
    }
}

I see. You want some additional data to "hitchhike" on some other implementation you can't change.

I assume you don't have control over the format of the Data struct, otherwise you'd just add your state there.

What you can do depends on what the other crate's interface provides. Is the other crate public? I.e. does it need a &mut Vec<Data>, or some other object that implements some traits you might be able to implement yourself on another object you can make.

Or, is it possible to implement Hash on Data, and make Data be a key to a HashMap? I know that comes with a hefty performance penalty but in some cases it might be acceptable.

Yeah that could solve it, but I'm not sure how to fix up data_state when the other function could remove indexes in the middle of the vec.

Consider defining your own wrapper type around the two vectors with the appropriate push and other modification methods instead, then.

The function only takes in &mut OtherStruct to modify the data vec.
And sorry but I'm not familiar with Hash in rust yet so I'm not sure where to go with that.

Also yes the other crate is public, so I could modify it to take in my own type and use that, but I was wondering if there is some solution around this before resorting to it.

I'm thinking of doing this if I end up modifying the other crate's function.

Forking the other crate is probably a last resort. But definitely an option. Also there is a chance that others might also want the functionality you need, so it might be worth asking the crate author(s). Which crate is it?

As far as the implementing Hash idea, I made some leaps about what you are trying to do. Namely I assumed the other crate wouldn't tell you which indices it removed / created / modified, so it would be up to you to scan through the output from the other crate and figure that out.

HashMap can use any object that implements the Hash and Eq traits as a key.

use std::collections::HashMap;

#[derive(Default, Hash, Eq, PartialEq)]
struct Data {
    field1: i32,
    field2: String,
}

fn main() {
    
    let mut state_map = HashMap::new();
    state_map.insert(Data::default(), "the default".to_string());
    
    println!("{}", state_map.get(&Data::default()).unwrap());
}

Here are the details on how to implement Hash. Hash in std::hash - Rust

1 Like

Thank you. I'll try the Hash idea you suggested before resorting to forking the other crate.

What I'm making is part of a GUI app which uses the data of OtherStruct from another crate, and the State stores some menu state for each data index, so I don't think State and Vec<Data> should be mixed together in the crate with the other function since they are unrelated.

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.