I get the following error: "value moved here, in previous iteration of loop". I just want to understand, is it possible to prove that the attempt to move the value will happen only once? i is immutable and therefore cannot be changed, and the range goes through 0, so would it be okay to create a github issue asking for an enhancement?
struct S {
field: i32,
}
fn main() {
let s = S {field: 0};
let mut v: Vec<S> = vec![];
for i in 0..10 {
if i == 0 {
v.push(s);
}
/* this does not work either */
match i {
0 => { v.push(s); },
_ => (),
}
}
}
No, ownership is based on scopes, and can't depend on values. The analysis is shallow and works based on broad rules what code may do, not on detailed analysis of what it actually does.
You'll need to wrap it in Option, and use s.take().unwrap(). The optimizer may later do careful loop analysis to optimize that out.
Well, this code is a bit artificial so I’m not 100% how of any actual code that would “need” this kind of analysis would look like. Furthermore the reason why i == 0 can only happen once is a bit less trivial than one might think; ultimately iteration through ranges like 0..10 is implemented in the standard library, and not some kind of compiler magic.
In this particular case also, since its the first iteration, it seems so trivial just to move this first iteration out of the loop.
In the general case there’s always the possibility to use a dynamic check that would panic if you try to take the value twice. I mean something like this:
struct S {
field: i32,
}
fn main() {
let mut s = Some(S {field: 0});
let mut v: Vec<S> = vec![];
for i in 0..10 {
if i == 0 {
v.push(s.take().unwrap());
}
}
}
To answer a related question, note that there are versions of this where the control flow makes it clear that the move will only happen once, which rust does allow:
struct S {
field: i32,
}
fn main() {
let s = S {field: 0};
let mut v: Vec<S> = vec![];
for i in 0..10 {
if i == 0 {
v.push(s);
break; // NEW!
}
}
}
As far as I understand the borrow checker only looks at one function at a time. It does not attempt to follow things outside of a function.
So presumably one could find all kind of cases where one can tell pretty easily that a move will only happen once, or whatever, and wonder why the borrow checker does not see that as well.
Arguably enough code analysis could be thrown at the problem to find many of those cases.
Or one could argue: If it is so obvious to you that the situation cannot arise then why have you written your code that looks like it might arise? Would that not be an obfuscation that is better simplified?