I'd like to elaborate on the temporary lifetime extension mentioned above. You should watch out for storing values borrowed from a mutex, as this can easily lead to an unexpected deadlock:
let mutex = Mutex::new(Foo { x: 42 });
// Fine: we modify the value inside the mutex,
// then the mutex unlocks immediately.
mutex.lock().unwrap().x += 1;
mutex.lock().unwrap().x += 1;
// Fine: we copy the value from the mutex,
// then the mutex unlocks immediately.
// (Value inside the mutex is not changed.)
let mut a = mutex.lock().unwrap().x;
a += 1;
let mut b = mutex.lock().unwrap().x;
b += 1;
// Deadlock: we borrow the value from the mutex guard,
// and the mutex stays locked until the end of scope of `c`.
let c = &mut mutex.lock().unwrap().x;
*c += 1;
let d = &mut mutex.lock().unwrap().x;
*d += 1;
I prefer to separate locking from accessing to avoid this pitfall. For example, this is dangerous because you have to look at the signature of action() to figure out if the mutex unlocks immediately or not:
let something = mutex.lock().unwrap().action();
This is better:
let guard = mutex.lock().unwrap();
let something = guard.action();
Now the mutex is unlocked when guard is dropped, which is much more predictable.
Another related behaviour is _ = xxx vs _foo = xxx. I used to think that they behave the same way but the former is a temporary and the latter is an unnamed variable (don't think that's the right terminology). Consider:
let _ = f.lock().unwrap();
// ...
and
let _lock = f.lock().unwrap();
// ...
The former will unlock immediately, the latter will keep the lock until the end of scope.
Personally I prefer an explicit drop(lock) if I need to deal with explicit locks, it's clear what's happening, all variables are named and I can unlock a lock before the end of scope too. When using mutexes I generally use the guard pattern, so far I only needed explicit lock/drops when using semaphores.
I'd call _ a non-binding, like saying "ignore this part," whereas _foo is a normal binding that also has the convention of silencing "unused" warnings.
The difference with _ is more apparent in patterns where you're moving or copying only part of something that's already bound elsewhere, instead of a temporary expression. For example, if you have some tuple x: (Vec<u32>, Vec<u32>), then let (first, _) = x; will move x.0 and leave x.1 in place, whereas let (first, _second) = x will move both parts.
Is there a good use for let _ = foo();? By the sound of it it's equivalent of calling foo() and immediately discarding the result. I guess this was the main reason for my assumption and confusion, I thought that let will always assign the value somewhere on the stack, it'd be just unnamed variable.
I have #![deny(unused_must_use)], which means I must explicitly handle Results. Writing let _ = foo()?; let's me ignore a Result in those few cases where it makes sense.
Isn't it defeating the purpose of the unused_must_use? You're not using the result, you're just discarding it in a workaround way. Wouldn't it suffice to foo().unwrap(); or foo()?; to explicitly handle the Result?
As I said, "in the few cases where it makes sense." I want to know if I'm not consuming the Result almost all the time, but there are a few cases where I don't care.
A typical case where you don't care whether an operation failed can be sending on a channel. When dealing with channels, the correct failure handling can be quite varied, and just ignoring the error is reasonably common as the correct answer.