More about slice -> array conversions

Since about Rust V 1.0 I'd like some slice length analysis and inference in Rust, but after so much time I guess it's not going to happen... So I look for alternative solutions.

To better focus the discussion I use this as example code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-rust-6.html

This is a part of that code:

fn mand64(init_r: &[f64x2;32], init_i : f64x2, out : &mut [u8]) {
    let mut tmp_init_r = ZEROS;   	
    
    for i in 0..8 {
    	tmp_init_r.copy_from_slice(&init_r[4*i..4*i+4]);
        out[i] = mand8(&tmp_init_r, init_i);
    }
}

fn main(){
	let mut width = std::env::args_os().nth(1)
        .and_then(|s| s.into_string().ok())
        .and_then(|n| n.parse().ok())
        .unwrap_or(16000);
    width = (width+7) & !7;
    
    let mut r0 = vec![f64x2(0.0,0.0); width/2];
    let mut i0 = vec![0.0; width];

    for i in 0..width/2 {
        let x1 = (2*i) as f64;
        let x2 = (2*i+1) as f64;
        let k = 2.0 / (width as f64);
        r0[i] = f64x2(k*x1, k*x2) - f64x2(1.5,1.5);
        i0[2*i]    = k*x1 - 1.0;
        i0[2*i+1]  = k*x2 - 1.0;
    }

    let rows : Vec<_>  = if width%64==0 {
        // process 64 pixels (8 bytes) at a time
        (0..width).into_par_iter().map(|y|{
            let mut tmp_r0 = [f64x2(0.0,0.0);32];
            let mut row = vec![0 as u8; (width/8) as usize];
            let init_i = f64x2(i0[y], i0[y]);
            
            for x in 0..width/64{
                tmp_r0.copy_from_slice(&r0[32*x..32*x+32]);
                mand64(&tmp_r0, init_i, &mut row[8*x..8*x + 8]);                
            }
            row
        }).collect()
    }
    ...

That out[i] inside mand64 is bound checked (and after unrolling it becomes eight different calls to panic) even if you compile in release mode. This little problem is solved with a small change:

fn mand64(init_r: &[f64x2;32], init_i : f64x2, out : &mut [u8; 8]) {

But now the call of mand64 must change (this change actually seems to speed up a bit this benchmark code):

mand64(&tmp_r0, init_i, &mut row[8*x..8*x + 8].try_into().unwrap());

That's more or less equivalent to two unwraps:

mand64(&tmp_r0, init_i, row.get_mut(8*x .. 8*x + 8).unwrap().try_into().unwrap());

The Rustc compiler in release mode is able to remove this second unwrap but it leaves the first one (that is the bounds test).

I'd still like better ways to convert slices to arrays in various situations. A possible syntax to get a slice and turn it into an array that covers a subset of the use cases could be:

mand64(&tmp_r0, init_i, &mut row[8 * x; 8]);

But I guess no one will add a new Rust slicing syntax for such situations, so I designed two methods (one mut and one not mut):

mand64(&tmp_r0, init_i, row.array_from_mut(8 * x).unwrap());

In this specific case there's no need to specify the length as const generic value because it's inferred by the result. In other cases you specify it with a turbofish. The two slice methods could be like:

pub fn array_from<const LEN: usize>(&self, pos: usize) -> Option<&[T; LEN]> {
    if let Some(piece) = self.get(pos .. pos + LEN) {
        let temp_ptr: *const [T] = piece;
        unsafe {
            Some( std::mem::transmute(temp_ptr as *const [T; LEN]) )
        }
    } else {
        None
    }
}

pub fn array_from_mut<const LEN: usize>(&mut self, pos: usize) -> Option<&mut [T; LEN]> {
    if let Some(piece) = self.get_mut(pos .. pos + LEN) {
        let temp_ptr: *mut [T] = piece;
        unsafe {
            Some( std::mem::transmute(temp_ptr as *mut [T; LEN]) )
        }
    } else {
        None
    }
}

Do you like that?

What's wrong with the TryFrom impls?

1 Like

It's longer to write, contans two unwraps instead of one, requires more numbers in some cases (that can also be bug-prone), currently requires an import.