Idomatic partial copy with offset?

Would anyone be able to weigh in on the more idiomatic solution to my solutions for copying data? For reference I'm reading data into a vec from a file like so.

let mut file = File::open(path)?;
let mut buffer = Vec::new();

assert_le!(file.metadata()?.len(), MAX_PROGRAM_SIZE as u64);
file.read_to_end(&mut buffer)?;

I've seen 3 different ways this could be solved so far. One using enumerate, one using copy_from_slice and the other using zip. Is one of these methods preferred over the other, or is there a better way I haven't seen yet? I'm curious what the proper Rust approach would be. Any information would be greatly appreciated.

// Using enumerate
for (index, byte) in buffer.iter().enumerate() {
    self.memory[STARTING_PROGRAM_ADDRESS + index] = *byte;
}

// Using copy_from_slice
self.memory[STARTING_PROGRAM_ADDRESS..(STARTING_PROGRAM_ADDRESS + buffer.len())]
    .copy_from_slice(buffer.as_slice());

// Using zip
for (src, dst) in buffer
    .iter()
    .zip(self.memory.iter_mut().skip(STARTING_PROGRAM_ADDRESS))
{
    *dst = *src;
}

Firstly, at least there is a couple of improvements:

  1. This code can be slightly shorter:
// Using copy_from_slice
self.memory[STARTING_PROGRAM_ADDRESS..][..buffer.len()]
    .copy_from_slice(&buffer);
  1. skip can be inefficient as it has to iterate skipped elements. Using the iterator of the mutable slice directly:
// Using zip
for (src, dst) in buffer
    .iter()
    .zip(&mut self.memory[STARTING_PROGRAM_ADDRESS..])
{
    *dst = *src;
}

Index access can be less efficient as an out of bound check has to be done at each access rather than once at the beginning. The generated code can be seen here https://rust.godbolt.org/z/9jdCp4.

In this case, copy_from_slice, as it is the exact fitting method, is most idiomatic, I think. And using index access is most less idiomatic.

2 Likes

Thanks for responding so quickly. I really like copy_for_slice with your cleanup to make it shorter. Is that slice syntax documented somewhere? I don't see it in the Rust book under the slice type (The Slice Type - The Rust Programming Language) and I've only ever seen examples using the single bracket for a range. I didn't realize you could chain it together like that.

Also, just for clarification, is the first bracket in your example the starting point, then the second bracket is the length, similar to this?

// Start at the second position and take 8 elements?
self.memory[2..][..8]

You know that slice[start..] means a sub-slice slice[start], .., slice[slice.len()-1] and likewise slice[..end] means a sub-slice slice[0], .., slice[end-1] right?

slice[start..][..len] is just a combination of two, not a new syntax.

That makes way more sense when explained that way. It looked weird seeing it, but it's essentially just method chaining, but with slices.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.