Here's what the current Rust compiler actually does. Optimization level 3, checking enabled. The question is whether the Rust compiler can optimize out all the checks for a simple matrix multiply without loss of safety.
Rust doesn't know about multidimensional arrays, so they have to be supported in a library. This matrix representation was extracted from the "algebloat" crate:
pub struct Matrix
{ data: Vec,
nrow: usize,
ncol: usize
}
Access functions, get and set written in the obvious way:
#[inline]
pub fn get(&self, r: usize, c: usize) -> f64
{ assert!(r
Matrix multiply, written in the obvious way:
#[inline]
pub fn mult(&self, other: &Matrix, result: &mut Matrix)
{ assert!(self.ncol == result.ncol); // out of the loop checks
assert!(self.nrow == result.nrow);
assert!(self.ncol == other.nrow);
assert!(self.nrow == other.ncol);
for r in 0..self.nrow
{ for c in 0..self.ncol
{ let mut tot = 0.0;
for rr in 0..self.nrow
{ tot += self.get(rr,c) * other.get(r,rr); }
result.set(r, c, tot);
}
}
}
Generated code for the inner loop:
///
/// mult - matrix multiply, straightforward approach
///
#[inline]
pub fn mult(&self, other: &Matrix, result: &mut Matrix)
{ assert!(self.ncol == result.ncol); // out of the loop checks
assert!(self.nrow == result.nrow);
assert!(self.ncol == other.nrow);
assert!(self.nrow == other.ncol);
for r in 0..self.nrow
{ for c in 0..self.ncol
{ let mut tot = 0.0;
for rr in 0..self.nrow
{ tot += self.get(rr,c) * other.get(r,rr); }
result.set(r, c, tot);
}
}
}
Generated code for the inner loop. "rustc 1.14.0", optimization level "opt-level = 3", AMD64 instruction set. Debug mode, so asserts should be checked. (Not sure about this; it is
possible that opt-level=3, "Aggressive" disables some checking. Documentation is unclear on this.)
.LBB8_50:
.Ltmp254: ; in Matrix::get()
.loc 1 122 0 ; self.data[c + r * self.ncol]
movq %rdi, %rax
mulq %r11 ; doing the multiply for the subscript every time
jo .LBB8_74 ; and checking it for overflow
addq %rsi, %rax ; doing the add. No strength reduction
jb .LBB8_76 ; another check
.Ltmp255:
.loc 17 1362 0
cmpq %rax, %r9
jbe .LBB8_72 ; and another check
.Ltmp256:
.loc 17 1362 0 is_stmt 0
cmpq %rcx, %r15
jbe .LBB8_78 ; array overflow check
.Ltmp257:
.loc 1 188 0 is_stmt 1
incq %rdi
.Ltmp258:
.loc 1 122 0
movsd (%r12,%rax,8), %xmm1
.Ltmp259:
.loc 1 147 0
mulsd (%rbx,%rcx,8), %xmm1 ; The real work: floating multiply
addsd %xmm1, %xmm0 ; and the add
.Ltmp260:
.loc 18 746 0
incq %rcx
cmpq %r14, %rdi
jb .LBB8_50 ; loop counter check - required
This is relatively decent code. the compiler got rid of multiple checks on the same values. There was some strength reduction of indices, too; only the subscript that's traversing the "wrong way" generated a multiply. The ones that are advancing one element at a time along the underlying vector are just adds. About five instructions could come out, but it's not bad code.
I've seen FORTRAN compilers do this kind of matrix multiply with a five instruction inner loop on a mainframe, incrementing pointers in registers for both dimensions. That's the advantage of multidimensional array support.
The point I made about multidimensional arrays stands - the compiler didn't strength reduce the multiply and eliminate the rest of the checks. That requires inferring too much from the user's code.
On the other hand, the code is good enough that using "unsafe" for performance reasons is very seldom justified.