2

When you have two slices of the same type (and length!), Rust provides the copy_from_slice function to copy from one to the other.

But if my source slice is a &[u8] and my target slice is a &[u32], is there any simpler way to copy it than manually iterating? Because there's no chance of the source values being out of range I thought there might be, but I can't find one.

fn copy(source: &[u8], target: &mut [u32]) {
    for (i, &ch) in source.iter().enumerate() {
        target[i] = ch as u32;
    }
}
curiousdannii
  • 1,658
  • 1
  • 25
  • 40

2 Answers2

2

As outlined in answers to Efficiency: arrays vs pointers using zip instead of enumerate and manual indexing will be more efficient if the compiler is not able to optimize the latter into the former.

fn copy(source: &[u8], target: &mut [u32]) {
    for (&value, target) in source.iter().zip(target) {
        *target = value as u32;
    }
}
cafce25
  • 15,907
  • 4
  • 25
  • 31
1

That is about as good as you are going to get. Most of the other solutions are going to require just as much code as this. For example, you could turn it into an iterator chain to try to improve readability. That being said, the original was already quite readable, so this isn't much of an improvement.

fn copy(source: &[u8], target: &mut [u32]) {
    source
        .iter()
        .copied()
        .map(u32::from)
        .enumerate()
        .for_each(|(index, value)| target[index] = value);
}

Or as @ChayimFriedman mentioned, you could use itertools's set_from. Here is what that would look like:

fn copy(source: &[u8], target: &mut [u32]) {
    target.iter_mut().set_from(source.iter().map(|x| *x as u32));
}

Alternatively, you could also choose to make the function generic. However, at its core it is the same thing so I'm not sure if you really gain any readability from doing this.

fn copy_into_slice_from<A, B>(src: &[A], dst: &mut [B])
where
    A: Copy,
    B: From<A>,
{
    assert_eq!(src.len(), dst.len());

    src.iter()
        .copied()
        .enumerate()
        .for_each(|(index, value)| dst[index] = B::from(value));
}

I suppose another option would be some sort of map_between_slices function to try to save on the amount of code needed while maintaining readability?

/// Ex: map_between_slices(&src, &mut dst, |x| *x as u32);
fn map_between_slices<A, B, F>(src: &[A], dst: &mut [B], mut func: F)
where
    F: for<'a> FnMut(&'a A) -> B,
{
    assert_eq!(src.len(), dst.len());

    src.iter()
        .enumerate()
        .for_each(|(index, value)| dst[index] = func(value));
}
Locke
  • 7,626
  • 2
  • 21
  • 41
  • I assume that I'd have to do the same to convert a &[char] into a &[u32]? (To convert a string into UTF-32.) – curiousdannii Dec 19 '22 at 01:16
  • @curiousdannii In rust a `char` is not a byte, as not all characters fit into a single byte. The second half of this answer explains a bit more on how chars/strings work in Rust: https://stackoverflow.com/a/68231883/5987669 That being said, you are correct that this could be use with very few changes. – Locke Dec 19 '22 at 01:28
  • Yes I know, I can get a `&[char]` through `str.chars().collect()`. I've been looking but haven't found any nicer way turn a string into a `&[u32]`, probably because everyone wants to avoid UTF-32. But this is an old C API I'm porting so I can't do anything about that. – curiousdannii Dec 19 '22 at 01:37