Search code examples
rustsliceborrow-checker

Modify an array of slices/references


I am new to Rust, and I am having a hard time wrapping my head around some of the reference/borrow behavior.

Here is a snippet which does not compile. I am attempting to create an array of u64 "slices", whose values I want to modify, and then access later:

fn main() {
    // Initialize an array of "slices" with dummy data
    let mut refs: [&[u64]; 4] = [&[0; 2]; 4];

    // Write some computed values to the array
    for i in 0..refs.len() {
        // ...
        // some computation to get `x` and `y`
        // ...
        let data: [u64; 2] = [x as u64, y as u64];
        let data_ref: &[u64] = &data;
        refs[i] = data_ref;
    }

    // Read the computed values from the array
    println!("{}", refs[3][0]);
}

The compiler returns:

error[E0597]: `data` does not live long enough
  --> src\main.rs:8:32
   |
7  |         let data: [u64; 2] = [x as u64, y as u64];
   |             ---- binding `data` declared here
8  |         let data_ref: &[u64] = &data;
   |                                ^^^^^ borrowed value does not live long enough
9  |         refs[i] = data_ref;
10 |     }
   |     - `data` dropped here while still borrowed
...
13 |     println!("{}", refs[3][0]);
   |                    ------- borrow later used here

While I understand that data is scoped to live within the for loop, and that it will no longer exist outside of the for loop (so setting refs[i] to reference data should result in a compiler error since refs outlives data), I am unsure of an alternative way to correctly set values of refs.

What is the appropriate ("rusty") way to declare, initialize, then read "slice" values from an array like this?


Solution

  • The problem is slices reference data that lives "somewhere else". They also can't really outlive the data they reference.

    If you define

    let data: [u64; 2] = [x as u64, y as u64];
    

    in your loop then data dies with each iteration. Therefore

    you can't have

    let data_ref: &[u64] = &data;
    refs[i] = data_ref;
    

    as, when the loop finishes, it would make refs contain a reference to a piece of data that doesn't exist anymore (data).

    In your example you can replace slices with 2-element arrays, like so:

    fn main() {
        let mut arrays = [[0; 2]; 4];
    
        // Write some computed values to the array
        for i in 0..arrays.len() {
            // ...
            // some computation to get `x` and `y`
            // ...
            let x = 1;
            let y = 2;
            arrays[i] = [x, y];
        }
    
        // Read the computed values from the array
        println!("{}", arrays[3][0]);
    }
    

    the the code prints 1.

    If you truly need an array of slices (for example when you get refs from the outside) I think you likely need copy the data into the slices, like so:

    (Edit: added the explicit [&mut [u64]; 4] type after drewtato's note)

    fn main() {
        // Initialize an array of "slices" with dummy data
        // The array doesn't need to be mutable (we don't modify it)
        // The slices have to be mutable (we modify them)
        let refs: [&mut [u64]; 4] = [&mut [0; 2], &mut [0; 2], &mut [0; 2], &mut [0; 2]];
    
        // Write some computed values to the array
        for i in 0..refs.len() {
            // ...
            // some computation to get `x` and `y`
            // ...
            let x = 1;
            let y = 2;
            refs[i].copy_from_slice(&[x, y]);
        }
    
        // Read the computed values from the array
        println!("{}", refs[3][0]);
    }
    

    (I have this ugly [&mut [0; 2], &mut [0; 2], &mut [0; 2], &mut [0; 2]] form in there because [&mut [0; 2]; 4] won't work – mutable array references aren't Copy so it's not so easy to initialize refs that way).

    See copy_from_slice documentation for details.