Let's tackle these one-by-one.
print_max_1
Here, largest is a mutable variable that holds an immutable reference to a u16 (i.e. it holds a &u16). Within the loop, number is also a &u16 which, like largest, is borrowed from number_list. Both number and larger are implicitly dereferenced to perform the comparison. If the value referenced by number is greater than that referenced by larger, you store a copy of the immutable reference contained in number into largest.
print_max_2
In this case, since number_list is itself borrowed, the analysis of print_max_2 is identical to print_max_1.
print_max_3
Here, largest is a u16. You are correct that number_list[0] is copied, but it is worth noting that this copy is cheap. Within the loop, each element of number_list is copied and stored directly in number. If number is greater than largest, you stored the new greatest value directly in largest. This is the most optimal of the three implementations you've written, since you do away with all of the unnecessary indirection that references (i.e., pointers) introduce.
print_max_4
Once again, you store a reference to the first element of number_list in largest, i.e. largest is a &u16. Similarly, as was the case in print_max_3, number is a u16, which will hold copies of the elements from number_list. However, as you noted, this function is the problem child.
Within the loop, the compiler will point out two errors:
- You attempt to compare two distinct types which don't have a
PartialOrder defined, namely largest which is a &u16 and number which is a u16. Rust isn't in the business of trying to guess what you mean by this comparison, so in order fix this, you'll have to make both number and largest the same type. In this case, what you want to do is explicitly dereference largest using the * operator, which will allow you to compare the u16 it points to with the u16 contained in number. This final comparison looks like
if number > *largest { ... }
- You attempt to store a
u16 in a variable of type &u16, which does not make sense. Unfortunately, here you're going to run into a wall. Within the loop, all you have is the value of the number you copied from number_list, but largest needs to hold a reference to a u16. We can't simply borrow number here (e.g. by writing largest = &number), since number will be dropped (i.e. go out of scope) at the end of the loop. The only way to resolve is is to revert by to print_max_2 by storing the maximum value itself instead of the pointer to it.
As for whether for number in number_list is a shortcut for for number in number_list.iter(), the answer is a firm no. The former will take ownership of number_list, and during each iteration, number takes ownership of the next value in number_list. In contrasts, the latter only performs a borrow, and during each iteration of the loop, number receives an immutable reference to the next element of number_list.
In this specific case, these two operation appear identical, since taking ownership of an immutable reference simply entails making a copy, which leaves the original owner intact. For more information, see this answer to a related question on the difference between .into_iter() and .iter().