Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the equivalent of a safe memset for slices?

Tags:

rust

In many cases, I need to clear areas of buffers or set a slice to certain value. What is the native recommended way of doing this?

This is invalid Rust, but I would like to do something similar to this:

let mut some_buffer = vec![0u8; 100];
buffer[10..20].set(0xFF)

I could use a for loop but I have the feeling I am missing something given that I am new to Rust.

In C++, I would do something like:

std::array<int,6> foobar;
foobar.fill(5);

In Python, it would be similar:

tmp = np.zeros(10)
tmp[3:6]=2
like image 736
Juan Leni Avatar asked Aug 07 '18 17:08

Juan Leni


2 Answers

As of Rust 1.50.0, released on 2021-02-11, slice::fill is now stable, meaning your example now works if you change the function name:

let mut buffer = vec![0u8; 20];
buffer[5..10].fill(0xFF);
println!("{:?}", buffer);

Will print [0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

like image 192
pheki Avatar answered Oct 26 '22 22:10

pheki


You aren't the only one. A feature request / RFC exists for the same thing:

  • Safe memset for slices #2067

However, you are putting the cart before the horse. Do you really care that it calls memset? I would guess not, just that it's efficient. A big draw of Rust is that the compiler can "throw away" many abstractions at build time. For example, why call a function when some CPU instructions will do the same thing?

pub fn thing(buffer: &mut [u8]) {
    for i in &mut buffer[10..20] { *i = 42 }
}
playground::thing:
    pushq   %rax
    cmpq    $19, %rsi
    jbe .LBB0_1
    movabsq $3038287259199220266, %rax
    movq    %rax, 10(%rdi)
    movw    $10794, 18(%rdi)
    popq    %rax
    retq

.LBB0_1:
    movl    $20, %edi
    callq   core::slice::slice_index_len_fail@PLT
    ud2

pub fn thing(buffer: &mut [u8]) {
    for i in &mut buffer[10..200] { *i = 99 }
}
.LCPI0_0:
    .zero   16,99

playground::thing:
    pushq   %rax
    cmpq    $199, %rsi
    jbe .LBB0_1
    movaps  .LCPI0_0(%rip), %xmm0
    movups  %xmm0, 184(%rdi)
    movups  %xmm0, 170(%rdi)
    movups  %xmm0, 154(%rdi)
    movups  %xmm0, 138(%rdi)
    movups  %xmm0, 122(%rdi)
    movups  %xmm0, 106(%rdi)
    movups  %xmm0, 90(%rdi)
    movups  %xmm0, 74(%rdi)
    movups  %xmm0, 58(%rdi)
    movups  %xmm0, 42(%rdi)
    movups  %xmm0, 26(%rdi)
    movups  %xmm0, 10(%rdi)
    popq    %rax
    retq

.LBB0_1:
    movl    $200, %edi
    callq   core::slice::slice_index_len_fail@PLT
    ud2

As kazemakase points out, when the set region becomes "big enough", the optimizer switches to using memset instead of inlining the instructions:

pub fn thing(buffer: &mut [u8]) {
    for i in &mut buffer[11..499] { *i = 240 }
}
playground::thing:
    pushq   %rax
    cmpq    $498, %rsi
    jbe .LBB0_1
    addq    $11, %rdi
    movl    $240, %esi
    movl    $488, %edx
    callq   memset@PLT
    popq    %rax
    retq

.LBB0_1:
    movl    $499, %edi
    callq   core::slice::slice_index_len_fail@PLT
    ud2

You can wrap this function in an extension trait if you'd like:

trait FillExt<T> {
    fn fill(&mut self, v: T);
}

impl FillExt<u8> for [u8] {
    fn fill(&mut self, v: u8) {
        for i in self {
            *i = v
        }
    }
}

pub fn thing(buffer: &mut [u8], val: u8) {
    buffer[10..20].fill(val)
}

See also:

  • Creating a vector of zeros for a specific size
  • Efficiently insert or replace multiple elements in the middle or at the beginning of a Vec?
like image 31
Shepmaster Avatar answered Oct 26 '22 22:10

Shepmaster