How to idiomatically copy a slice?
Asked Answered
M

3

62

In Go, copying slices is standard-fare and looks like this:

# It will figure out the details to match slice sizes
dst = copy(dst[n:], src[:m])

In Rust, I couldn't find a similar method as replacement. Something I came up with looks like this:

fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize {
    let mut c = 0;
    for (&mut d, &s) in dst.iter_mut().zip(src.iter()) {
        d = s;
        c += 1;
    }
    c
}

Unfortunately, I get this compile-error that I am unable to solve:

error[E0384]: re-assignment of immutable variable `d`
 --> src/main.rs:4:9
  |
3 |     for (&mut d, &s) in dst.iter_mut().zip(src.iter()) {
  |               - first assignment to `d`
4 |         d = s;
  |         ^^^^^ re-assignment of immutable variable

How can I set d? Is there a better way to copy a slice?

Molecular answered 29/1, 2015 at 16:5 Comment(1)
Could you expand a bit on why you want to copy data in slices around? I'd usually expect to either just take a reference to the original or to be copying the data to something that owns the copy.Seagrave
R
104

Yes, use the method clone_from_slice(), it is generic over any element type that implements Clone.

fn main() {
    let mut x = vec![0; 8];
    let y = [1, 2, 3];
    x[..3].clone_from_slice(&y);
    println!("{:?}", x);
    // Output:
    // [1, 2, 3, 0, 0, 0, 0, 0]
}

The destination x is either a &mut [T] slice, or anything that derefs to that, like a mutable Vec<T> vector. You need to slice the destination and source so that their lengths match.


As of Rust 1.9, you can also use copy_from_slice(). This works the same way but uses the Copy trait instead of Clone, and is a direct wrapper of memcpy. The compiler can optimize clone_from_slice to be equivalent to copy_from_slice when applicable, but it can still be useful.

Roundy answered 29/1, 2015 at 21:17 Comment(1)
Thank you ! This was what I was initially looking for, but couldn't find as I was searching for anything related to copy. In rust-speak, clone might be the appropriate term though ... I am still getting used to it all.Molecular
M
6

This code works, even though I am not sure if it the best way to do it.

fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize {
    let mut c = 0;
    for (d, s) in dst.iter_mut().zip(src.iter()) {
        *d = *s;
        c += 1;
    }
    c 
}

Apparently not specifying access permissions explicitly did the trick. However, I am still confused about this and my mental model doesn't yet cover what's truly going on there. My solutions are mostly trial and error when it comes to these things, and I'd rather like to truly understand instead.

Molecular answered 29/1, 2015 at 16:38 Comment(2)
Your approach is correct. Your initial version didn't work because dereference patterns (like &mut d) creates a new variable (d in this case) and assigns it to a dereference of the pointer. It does not provide an ability to modify the original value, it just copies the original value; if you used it with non-Copy type, your program won't even compile.Plossl
BTW, you can also keep &s in place and write *d = s afterwards.Plossl
A
-1

Another variant would be

fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize {
    dst.iter_mut().zip(src).map(|(x, y)| *x = *y).count()
}

Note that you have to use count in this case, since len would use the ExactSizeIterator shortcut and thus never call next, resulting in a no-op.

Aboriginal answered 25/3, 2017 at 23:36 Comment(7)
This is not idiomatic Rust. Iterators should generally not have side-effects, and the usage of map here doubly non-idomatic. If that's what you want, a for loop is the correct choice.Seagrave
I admit this is kind of a hack, but the for loop requires manual tracking of the number of iterations. Instead of map one could probably use inspect to make it clearer that the „result“ will get discarded.Aboriginal
@Seagrave You may want to consider the following example that's included in the official Rust docs for collect(), which shows the following: let hello: String = chars.iter().map(|&x| x as u8).map(|x| (x + 1) as char).collect();Cunha
@Cunha thanks, but this isn't my answer — I'm only an editor here.Seagrave
@Seagrave No worries. I'm not defending this implementation either, or anything. I just wanted to note the detail w.r.t. the lack of side-effects for iterators and/or maps.Cunha
@Cunha oh, I see what you are saying now (I thought you were saying to put that example in the answer). The example you cite does not have side effects — it only operates on the values provided to the closures and those values are not mutable references.Seagrave
Just as an extension on Shepmaster's first comment, here's a quote from the Rust reference on the Iterator trait: map() is conceptually similar to a for loop. However, as map() is lazy, it is best used when you're already working with other iterators. If you're doing some sort of looping for a side effect, it's considered more idiomatic to use for than map()..Diviner

© 2022 - 2024 — McMap. All rights reserved.