Conceptually, Rust performs an array binding check for each access to each array. However, the compiler is very good at optimizing checks when it can prove that it is safe.
LLVM's staging output is misleading because it is still being optimized using the LLVM optimization machine prior to assembly. The best way to verify the assembly is to create the final assembly using a call such as rustc -O --emit asm --crate-type=lib . The build result for your function :
push rbp mov rbp, rsp mov eax, 2 pop rbp ret
There is not only a binding check, but also no array, the compiler optimized the entire function until return 2i32 ! To force a check to be bound, the function must be written so that Rust cannot prove that it can be removed:
pub fn test_dynamic_checking(ind: usize) -> i32 () { let x = [1, 2, 3, 4]; x[ind] }
This leads to a larger assembly where the binding check is performed as the following two instructions:
cmp rax, 3 ; compare index with 3 ja .LBB0_2 ; if greater, jump to panic code
It is as effective as it gets. Turning off snap checking is rarely a good idea, because it can lead to a program crash. This can be done, but explicitly and only within the block or function unsafe :
pub unsafe fn test_dynamic_checking(ind: usize) -> i32 () { let x = [1, 2, 3, 4]; *x.get_unchecked(ind) }
The generated assembly shows that the error is completely eliminated:
push rbp mov rbp, rsp lea rax, [rip + const3141] mov eax, dword ptr [rax + 4*rdi] pop rbp ret const3141: .long 1 .long 2 .long 3 .long 4