rustmutable

Best way to have a nested mutable data element in rust


I'm trying to figure out the best way to handle some nested mutable changes. I've got the following code that mimicks the issue I'm running into:

use std::collections::HashMap;
use circular_buffer::CircularBuffer;

#[tokio::main]
async fn main() {

    let mut hash:HashMap<u8, CircularBuffer<200, f32>> = HashMap::new();

    for idx in 0..10 {
        let mut buffer = CircularBuffer::<200, f32>::new();
        buffer.push_front(14.0 * idx as f32 + 2.0);
        buffer.push_front(19.4 * idx as f32 + 4.19);
        hash.insert(idx, buffer);
    }

    tokio::spawn(async move {
        for (key, mut value) in hash {
            println!("key: {}, buffer_length: {}", key, value.len());
            let mut first = value[0];
            first = 24.0 * key as f32;
        }
        for (key, mut value) in hash {
            println!("key: {}, buffer_length: {}", key, value.len());
            let mut first = value[0];
            println!("\tFirst: {}", first);
        }

    });

}

You can ignore the data entered into the hash -- I'm just putting dummy data there. In reality, it's a custom struct, and I need to modify items inside the structs. The code below gives the exact same compiler error, so I think the example is sufficient.

What's the best way to iterate through hash so that I can modify the data as needed? The compiler suggests adding an & to the beginning of hash, but that doesn't really resolve the issue, either. I'm probably looking at this incorrectly. Suggestions would be very much appreciated.


Solution

  • Here you go:

    use circular_buffer::CircularBuffer;
    use std::collections::HashMap;
    
    #[tokio::main]
    async fn main() {
        let mut hash: HashMap<u8, CircularBuffer<200, f32>> = HashMap::new();
    
        for idx in 0..10 {
            let mut buffer = CircularBuffer::<200, f32>::new();
            buffer.push_front(14.0 * idx as f32 + 2.0);
            buffer.push_front(19.4 * idx as f32 + 4.19);
            hash.insert(idx, buffer);
        }
    
        let task = tokio::spawn(async move {
            for (key, value) in &mut hash {
                println!("key: {}, buffer_length: {}", key, value.len());
                let first = &mut value[0];
                *first = 24.0 * *key as f32;
            }
            for (key, value) in &mut hash {
                println!("key: {}, buffer_length: {}", key, value.len());
                let first = value[0];
                println!("\tFirst: {}", first);
            }
        });
    
        task.await.unwrap();
    }
    
    key: 6, buffer_length: 2
    key: 4, buffer_length: 2
    key: 7, buffer_length: 2
    key: 8, buffer_length: 2
    key: 5, buffer_length: 2
    key: 3, buffer_length: 2
    key: 0, buffer_length: 2
    key: 2, buffer_length: 2
    key: 9, buffer_length: 2
    key: 1, buffer_length: 2
    key: 6, buffer_length: 2
            First: 144
    key: 4, buffer_length: 2
            First: 96
    key: 7, buffer_length: 2
            First: 168
    key: 8, buffer_length: 2
            First: 192
    key: 5, buffer_length: 2
            First: 120
    key: 3, buffer_length: 2
            First: 72
    key: 0, buffer_length: 2
            First: 0
    key: 2, buffer_length: 2
            First: 48
    key: 9, buffer_length: 2
            First: 216
    key: 1, buffer_length: 2
            First: 24
    

    Explanation

    Then, to edit a mutable reference, you need to dereference it via *first =.

    The main difference between using f32 and a custom struct is that f32 is Copy, so you need to be careful that you actually edit the original object and not a copy. That's why it's important that first is &mut f32 and not mut f32.

    Additional Tips

    Use an IDE like VSCode together with the rust-analyzer extension. That shows you all the types inline, making it very easy to understand what's going on:

    rust-analyzer type annotations