5

I'm trying to copy the content of a Vec into an existing Vec, replacing the content of the target Vec.

Here is what I'm looking for:

  • allocation of new memory shouldn't be needed (unless the target Vec is too short) because the target Vec is already allocated
  • it should not use an iterator because a memcopy should be good enough do the job
  • the source Vec shouldn't be changed
  • ideally it should use safe methods

Here is what I tried:

  • vec.clone() : gives correct content but allocates new memory, which should not be needed for a copy into an already existing big enough Vec
  • vec.clear(); vec.extend(); : copies in place, but seems to use an iterator on each element, which should not be needed, I just want a memcopy
  • vec.copy_from_slice() : is what I'm looking for, but needs exactly the same size buffers, which for some reason I can't seem to get

What doesn't work

#![feature(shrink_to)]
fn vec_copy(src: &Vec<i32>, dst: &mut Vec<i32>) {
    // Try to adjust dst buffer size... there should be a better way
    if src.len() > dst.len() {
        let addon = src.len() - dst.len();
        dst.reserve_exact(addon);
    } else {
        dst.shrink_to(src.len());
    }
    // Do the copy        
    // panics! :
    // thread 'main' panicked at libcore/slice/mod.rs:1645:9
    // 'destination and source slices have different lengths'
    // 
    dst.copy_from_slice(src.as_slice()); // <--- panics here
}

fn main() {
    // Copy from a shorter Vec
    let mut container = vec![1, 2];
    let test1 = vec![3]; // shorter vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7f00bda20008 = [1, 2]
    vec_copy(&test1, &mut container); // panics inside function 
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7f00bda20008 = [3]

    // Copy from a longer Vec
    container = vec![1, 2];    
    let test2 = vec![4, 5, 6]; // longer Vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7fef5b820018 = [1, 2]
    vec_copy(&test2, &mut container); // panics inside function
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7fef5b820018 = [4, 5, 6]    
}

Panics with error:

thread 'main' panicked at libcore/slice/mod.rs:1645:9,
'destination and source slices have different lengths'

The question

Using vec.copy_from_slice() seems to be the way to memcopy in place the content of a Vec into an other, without unneeded memory allocation and without using an iterator.

How can I set the size of the target Vec so that vec.copy_from_slice() doesn't panic?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jocelyn
  • 1,297
  • 12
  • 20
  • 1
    *it should not use an iterator because a memcopy should be good enough do the job* — this presupposes that an iterator precludes a memcpy which is not the case. – Shepmaster Jun 05 '18 at 19:45
  • @Shepmaster yes, I had the preconceived idea that an iterator (because of the name) would copy each element independently inside a loop instead of doing one big memcpy. Today I learnt. – Jocelyn Jun 06 '18 at 04:41

2 Answers2

4

You want Vec::extend_from_slice:

fn vec_copy(src: &[i32], dst: &mut Vec<i32>) {
    // Optionally truncate to zero if there might be existing data
    // dst.clear();
    dst.extend_from_slice(src);
}

This avoids needing to fill the vector with "dummy values" when adding more elements than were previously allocated .

If you look at the assembly in release mode, it calls memcpy after establishing that there is enough allocated space:

playground::vec_copy:
    pushq   %r15
    pushq   %r14
    pushq   %r12
    pushq   %rbx
    subq    $88, %rsp
    movq    %rdx, %rbx
    movq    %rsi, %r15
    movq    %rdi, %r14
    movq    8(%rbx), %rsi
    movq    16(%rbx), %r12
    movq    %rsi, %rax
    subq    %r12, %rax
    cmpq    %r15, %rax
    jae .LBB1_14
    addq    %r15, %r12
    jb  .LBB1_8
    leaq    (%rsi,%rsi), %rax
    cmpq    %rax, %r12
    cmovbq  %rax, %r12
    movl    $4, %ecx
    movq    %r12, %rax
    mulq    %rcx
    jo  .LBB1_8
    testq   %rsi, %rsi
    je  .LBB1_9
    shlq    $2, %rsi
    movq    (%rbx), %rdi
    movq    %rsp, %r9
    movl    $4, %edx
    movl    $4, %r8d
    movq    %rax, %rcx
    callq   __rust_realloc@PLT
    testq   %rax, %rax
    jne .LBB1_5
    movq    (%rsp), %rax
    jmp .LBB1_12

.LBB1_9:
    movq    %rsp, %rdx
    movl    $4, %esi
    movq    %rax, %rdi
    callq   __rust_alloc@PLT
    testq   %rax, %rax
    je  .LBB1_12

.LBB1_5:
    xorl    %ecx, %ecx
    movdqa  32(%rsp), %xmm0
    movdqa  %xmm0, 48(%rsp)
    testq   %rcx, %rcx
    je  .LBB1_13

.LBB1_6:
    movq    %rax, (%rsp)
    movaps  48(%rsp), %xmm0
    movups  %xmm0, 8(%rsp)
    leaq    64(%rsp), %rdi
    movq    %rsp, %rsi
    callq   <core::heap::CollectionAllocErr as core::convert::From<core::heap::AllocErr>>::from@PLT
    movdqa  64(%rsp), %xmm0
    movq    %xmm0, %rax
    cmpq    $3, %rax
    je  .LBB1_14
    cmpq    $2, %rax
    jne .LBB1_15

.LBB1_8:
    leaq    .Lbyte_str.5(%rip), %rdi
    callq   core::panicking::panic@PLT
    ud2

.LBB1_12:
    movups  8(%rsp), %xmm0
    movaps  %xmm0, 32(%rsp)
    movl    $1, %ecx
    movdqa  32(%rsp), %xmm0
    movdqa  %xmm0, 48(%rsp)
    testq   %rcx, %rcx
    jne .LBB1_6

.LBB1_13:
    movq    %rax, (%rbx)
    movq    %r12, 8(%rbx)

.LBB1_14:
    movq    16(%rbx), %rdi
    leaq    (%rdi,%r15), %rax
    movq    %rax, 16(%rbx)
    shlq    $2, %r15
    shlq    $2, %rdi
    addq    (%rbx), %rdi
    movq    %r14, %rsi
    movq    %r15, %rdx
    callq   memcpy@PLT
    addq    $88, %rsp
    popq    %rbx
    popq    %r12
    popq    %r14
    popq    %r15
    retq

.LBB1_15:
    movq    80(%rsp), %rax
    movdqa  %xmm0, (%rsp)
    movq    %rax, 16(%rsp)
    movq    %rsp, %rdi
    callq   <alloc::heap::Heap as core::heap::Alloc>::oom
    ud2

println!("{:p} = {:?}", &container[0], container);

container.as_ptr() is more obvious than &container[0] and will not fail when the vector is empty.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks for the various tips! I was worried that `extend_from_slice()` would be copying each element independently inside a loop. I was confused by the [documentation](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.extend_from_slice) which states _"Iterates over the slice other, clones each element, and then appends it to this Vec"_. But it ends up calling `copy_from_slice()` internally and doing a unique memcpy which is exactly what I was looking for. Before copying it does a `reserve()` and `set_len()`, which is why we can use `truncate()` before. – Jocelyn Jun 06 '18 at 05:21
  • 1
    Note that `vec.truncate(0)` can be abbreviated as `vec.clear()`. – Jocelyn Jun 07 '18 at 09:24
2

You can use the Vec.resize() function:

fn vec_copy(src: &Vec<i32>, dst: &mut Vec<i32>) {
    dst.resize(src.len(), 0);
    dst.copy_from_slice(src.as_slice());
}

fn main() {
    // Copy from a shorter Vec
    let mut container = vec![1, 2];
    let test1 = vec![3]; // shorter vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7f00bda20008 = [1, 2]
    vec_copy(&test1, &mut container); // panics inside function 
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7f00bda20008 = [3]

    // Copy from a longer Vec
    container = vec![1, 2];    
    let test2 = vec![4, 5, 6]; // longer Vec
    println!("{:p} = {:?}", &container[0], container); // output: 0x7fef5b820018 = [1, 2]
    vec_copy(&test2, &mut container); // panics inside function
    println!("{:p} = {:?}", &container[0], container); // expected: 0x7fef5b820018 = [4, 5, 6]    
}

Output:

0x7f2e36020008 = [1, 2]
0x7f2e36020008 = [3]
0x7f2e36020028 = [1, 2]
0x7f2e3602a010 = [4, 5, 6]

playground

The final one has a different address presumably because the destination had a smaller capacity than the source, so it had to be grown which means reallocation. This can be shown by printing out the destination's capacity, and observing that it grew from 2 -> 4:

println!("{:p} = {:?} (capacity: {:?})", &container[0], container, container.capacity());
vec_copy(&test2, &mut container); // panics inside function
println!("{:p} = {:?} (capacity: {:?})", &container[0], container, container.capacity());

Output:

0x7f16a1820008 = [1, 2]
0x7f16a1820008 = [3]
0x7f16a1820028 = [1, 2] (capacity: 2)
0x7f16a182a010 = [4, 5, 6] (capacity: 4)

playground

You could prevent that by ensuring that the destination is large enough at the time of creation with Vec::with_capacity.

Jorge Israel Peña
  • 36,800
  • 16
  • 93
  • 123
  • Thanks! Yes, address is different when destination is smaller because of the realloc, I don't know why I missed that. Your solution works, I ended up accepting @Shipmaster 's answer because it doesn't need to fill destination with dummy values in case of realloc (which here `resize()` does) and it still uses `copy_from_slice()` internally. – Jocelyn Jun 06 '18 at 04:32
  • 1
    Sure no worries, I understand and agree. Glad to help! – Jorge Israel Peña Jun 06 '18 at 04:32