OK, didn't know how to title it but in a nutshell:
let mut handles:Vec<JoinHandle<()>> = Vec::new();
while let Some(v) = rx.recv().await
{
if v.len() == 0
{
for handle in handles
{
handle.await;
}
return Ok(());//comment that out and you will get error talking about using moved value, i.e. handles
}
handles.push(tokio::spawn(async move {
}));
}
I am really, really confused by this. Why without return this code is invalid and with return this code is valid?
Why wouldn’t it be valid? The compiler is smart enough to figure out that if there’s an unconditional return (or any divergence, break would work here too), handles is only ever consumed once, and the code is fine. Whereas without the return it must assume that the if body is reached twice and tries to access handles after it was moved from in an earlier iteration, which it cannot allow.
The compiler does not reason about conditions like v.len() > 0 or v.len() > 10 etc etc, it just looks at what code paths are possible. In particular here the execution flow has 2 possibilities: enter the if or not. The compiler doesn't know which one will actually happen, so it has to check both of them to ensure that everything is fine is both cases:
If it doesn't then handles remains valid and everything is good.
If however it enters the if then handles is consumed.
In the code without the return this is problematic because from there the code flow continues until the handles.push(...), which requires handles to be valid, hence the error
In the code with the return, this is not an issue, because there's no way for the execution to reach the handles.push(...) after consuming handles in the for loop, since it will always hit the return and exit from the function before that can happen.
In the version without the return there's also another issue that can be seen by commenting out the handles.push(...): after an iteration is executed the outer while let will run again, and this may reach the for handle in handles again, which is problematic because handles was consumed in the prior iteration. The way the compiler sees this is by rerunning the analysis on the loop body after finding out which variables are consumed in the first analysis run. More in general this falls under the umbrella of dataflow analysis.