Rust Locker Problem Number Theory The Locker Problem is as followed:

Twenty bored students take turns walking down a hall that contains
a row of closed lockers, numbered 1 to 20. The first student opens all the
lockers; the second student closes all the lockers numbered 2, 4, 6, 8, 10, 12, 14,
16, 18, 20; the third student operates on the lockers numbered 3, 6, 9, 12, 15, 18:
if a locker was closed, he opens it, and if a locker was open, he closes it; and so
on. For the ith student, he works on the lockers numbered by multiples of i: if a
locker was closed, he opens it, and if a locker was open, he closes it. What is the
number of the lockers that remain open after all the students finish their walks?

I decided to take this problem the extra mile and instead allow the user determine how many students were in the hallway so the number isn't always 20. In what ways can I refactor my code?
use std::io;

fn main() {
    // false = closed
    // true = opened
    let mut lockercount = String::new();
    println!(&quot;How many students?&quot;);
    io::stdin().read_line(&amp;mut lockercount).expect(&quot;expected int&quot;);
    let lc = lockercount.trim().parse().unwrap();
    let mut lockers = vec![false; lc];

    for i in 1..(lockers.len()+1) {
        let mut j: usize = 1;
        while i*j &lt;= lc {
            lockers[(i*j)-1] = !lockers[(i*j)-1];
            j += 1;
        }
    }
    
    let mut lockernum = 1;
    println!(&quot;Opened lockers:&quot;);
    for v in lockers {
        if v == true {
            print!(&quot;{}, &quot;, lockernum);
        }
        lockernum += 1;
    }
}
Your code seems fine. However, there is one part I'd definitely change: the scope of variables. For example, lockercount isn't necessary for more than the initial lc. We can keep its scope down:
    println!(&quot;How many students?&quot;);
    let lc = {
        let mut lockercount = String::new();
        io::stdin().read_line(&amp;mut lockercount).expect(&quot;expected int&quot;);
        lockercount.trim().parse().unwrap()
    };

Similarly, I would prefer to use enumerate instead of an explicit counter. An explicit counter might not be incremented by accident, but enumerate on an iterator will always fit:
    println!(&quot;Opened lockers:&quot;);
    for (n, v) in lockers.iter().enumerate() {
        if *v == true {
            print!(&quot;{}, &quot;, n + 1);
        }
    }

We could also get rid of the if via filter, but that's a little bit much:
    for (n, _v) in lockers.iter().enumerate().filter(|(_, &amp;b)| b) {
        print!(&quot;{}, &quot;, n + 1);
    }

On the algorithmic part of the code, I'd prefer to use the position explicitly instead of i*j:
    for i in 1..(lockers.len() + 1) {
        let mut pos: usize = i;
        while pos &lt;= lc {
            lockers[pos - 1] = !lockers[pos - 1];
            pos += i;
        }
    }

Again, instead of explicitly changing a counter, I would prefer an immutable variable, for example via step_by:
    for i in 1..(lockers.len() + 1) {
        for pos in (i..lc).step_by(i) {
            lockers[pos - 1] = !lockers[pos - 1];
        }
    }