A little function with plenty of slice bound tests

This little coding problem asks to rotate a square matrix by 90 degrees clockwise:

A simple Rust port of that C++ solution could be:

pub unsafe fn rotate1(m: &mut [&mut [u32]]) {
    let s = m.len();
    for i in 0 .. s / 2 {
        for j in i .. s - 1 - i {
            assume(i < m.len());
            assume(j < m[i].len());
            let tmp = m[i][j];
            assume(s - 1 - j < m.len());
            assume(i < m[s - 1 - j].len());
            m[i][j] = m[s - 1 - j][i];
            assume(s - 1 - j < m[s - 1 - i].len());
            m[s - 1 - j][i] = m[s - 1 - i][s - 1 - j];
            assume(j < m.len());
            assume(s - 1 - i < m[j].len());
            m[s - 1 - i][s - 1 - j] = m[j][s - 1 - i];
            m[j][s - 1 - i] = tmp;
        }
    }
}

If you remove those assume() you get many slice bound tests.

A const generics version needs none of those assume(), surprisingly in this function the back-end is able to remove all slice bound tests regardless the value of S:

pub fn rotate2<const S: usize>(m: &mut [&mut [u32; S]; S]) {
    for i in 0 .. S / 2 {
        for j in i .. S - 1 - i {
            let tmp = m[i][j];
            m[i][j] = m[S - 1 - j][i];
            m[S - 1 - j][i] = m[S - 1 - i][S - 1 - j];
            m[S - 1 - i][S - 1 - j] = m[j][S - 1 - i];
            m[j][S - 1 - i] = tmp;
        }
    }
}

This is because the nested arrays are not forced to be the same length as the outer one and could be smaller, thus causing memory unsafety

Yes, I should add a pre-condition to assert that all the rows have the same length:

#![feature(core_intrinsics)]
use std::intrinsics::assume;

pub unsafe fn rotate3(m: &mut [&mut [u32]]) {
    let s = m.len();
    assert!(m.iter().all(|r| r.len() == s));

    for i in 0 .. s / 2 {
        for j in i .. s - 1 - i {
            assume(i < m.len());
            assume(j < m[i].len());
            let tmp = m[i][j];
            assume(i < m.len());
            assume(j < m[i].len());
            assume(s - 1 - j < m.len());
            assume(i < m[s - 1 - j].len());
            m[i][j] = m[s - 1 - j][i];
            assume(s - 1 - i < m.len());
            assume(s - 1 - j < m[s - 1 - i].len());
            assume(s - 1 - j < m.len());
            assume(i < m[s - 1 - j].len());
            m[s - 1 - j][i] = m[s - 1 - i][s - 1 - j];
            assume(j < m.len());
            assume(s - 1 - i < m[j].len());
            assume(s - 1 - i < m.len());
            assume(s - 1 - j < m[s - 1 - i].len());
            m[s - 1 - i][s - 1 - j] = m[j][s - 1 - i];
            assume(j < m.len());
            assume(s - 1 - i < m[j].len());
            m[j][s - 1 - i] = tmp;
        }
    }
}

The C# language has built-in tensors, that are guaranteed to have same length rows, etc (CSharp/C# Tutorial - C# Multidimensional Arrays ). This should help the back-end infer some of those assume().

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.