How rust knows: "value captured here after move"?

I am reading documentation about concurrency, and I can not understand how rust "knows" what is and what is not dangerous. There is a code in the doc, which produces error to demonstrate that rust will detect race conditions:

use std::thread;
use std::time::Duration;

fn main() {
    let mut data = vec![1, 2, 3];

    for i in 0..3 {
        thread::spawn(move || {
            data[0] += i;
        });
    }

    thread::sleep(Duration::from_millis(50));
}

But documentation is very short on explanation what is the compiler logic, it just shares excitement about it being able to do it.

If I remove the loop, the code will compile. I do understand why in the loop is dangerous and without the loop is ok, but how compiler does it?

The compiler keeps track of moves via dataflow analysis. These are pretty well-known techniques used in compilers and optimizers.