let flag = true;
let owned = Some(String::from("asd"));
if let Some(s) = owned && flag {
println!("{s}");
} else {
println!("flag could be false but move 'occured': {owned:?}")
// borrow of partially moved value: `owned`
}
So, everything makes sense. The move occurs inside if statement: if *move* {...} else {}, therefore we can't use owned in else block.
But if I change the order of statements, everything works fine:
let flag = true;
let owned2 = Some(String::from("asd"));
if flag && let Some(s) = owned2 {
println!("{s}");
} else {
println!("flag could be false but move 'occured': {owned2:?}")
}
Does it mean that there is some rule saying "if let expression is the last in the chain, then the value is not considered "moved" for the else block"? Maybe there is some broader rule that results in this behaviour?
Is that a typo? Might be rule? But actually I don't understand it.
In their second example
if flag && let Some(s) = owned2 {
should be a match and a move. Actually flag is immutable, so the compiler should just remove it. I tested in playground with true instead, and got the same strange behaviour.
Interestingly, the two forms work with a test on None:
let flag = true;
let owned: Option<String> = Some(String::from("asd"));
if flag && let None = owned {
println!("{flag}: {owned:?}");
} else {
println!("{owned:?}")
}
and
let flag = true;
let owned: Option<String> = Some(String::from("asd"));
if let None = owned && flag {
println!("{flag}: {owned:?}");
} else {
println!("{owned:?}")
}
EDIT: I suppose we could say that, in the code of the first post,
in the first example, the compiler may move the value in the match, yet fail because of the flag, so there's a possibility the string has been moved when reaching the else clause,
in the second example, the compiler knows that in the else clause, either owned2 is None or hasn't moved the string otherwise because it stopped after failing the first condition
But it's less obvious with the two None tests above, unless a match with None doesn't move the data.
It looks like a clever optimization, but it's a little confusing semantically, so I suppose there's a better explanation.
I was not even aware that such a syntax is allowed
I thought the let construct should allow an assignment, but with "let None =" that would never happen.
Yes, it's arguably not very useful as a pattern when .is_none() would fit the bill.
But since it's a pattern, you can indeed use all the alternatives in the test, like in a match.
I'm talking about using a pattern to test if the value is None (cfr. the code I posted above). I'm not sure to see the relationship with what you wrote...
A move will only occur when one destructures the variable in a way that causes the data to be moved. So if let None = x has never moved the data for similar reasons as why destructuring with a wildcard pattern hasn't moved the data (e.g., if let Some(_) = x has never moved x ).
In fact when None and Some(_) are used, the variable exists beyond the entire if block:
fn main() {
let x = Some("hi".to_owned());
if let None = x {
}
if let Some(_) = x {
}
// `x` was never `move`d even in rustc 1.0.0.
drop(x);
}
// This compiles (`if flag && let Some(s) = owned`)
let flag = true;
let owned = Some(String::from("asd"));
if flag {
if let Some(s) = owned {
println!("{s}");
} else {
println!("flag could be false but move 'occured': {owned:?}")
}
}
// This does not compile (`if let Some(s) = owned && flag`)
let flag = true;
let owned = Some(String::from("asd"));
if let Some(s) = owned {
if flag {
println!("{s}");
} else {
println!("flag could be false but move 'occured': {owned:?}")
}
}
Only bindings (potentially) move. With the None variant, or other literals / consts, you're checking for structural equality. Or if you prefer: it's like matching against Some(_) or Struct { .. }, which also match against a value without moving it (and don't create any bindings).
This fails because you create a binding again.
let flag = true;
let owned: Option<String> = Some(String::from("asd"));
if let mv_it_plz @ None = owned && flag {
println!("{flag}: {owned:?}");
} else {
println!("{owned:?}")
}
In contrast with if let, the pattern in a let _ statement has to be infallible, so would need to cover all the possibilities. let statements also have few uses outside of creating bindings. So your intuition is not too surprising.
But if let takes a (potentially fallible) pattern, just like a match arm. This works and creates no bindings after all:
I've found non-binding if let ... else sometimes useful, and even non-binding let statements.
// Lots of fs/io operations return `Result<(), io::Error>`
// This makes blantantly clear that I'm not ignoring any data
let () = some_io_slash_fs_operation()?;
It is a little strange. If you don't like it, you can always use matches! instead.
One interesting tidbit is that you can do matches (in either way) in place of an equality check, when necessary because the enum doesn't implement PartialEq; this works because matching doesn't require PartialEq (except on inner values it must compare, of course).
Note that as @philomathic_life pointed out below, you shouldn't normally use matching as a replacement for == when PartialEqis implemented, since that implementation may not behave exactly the same as a match. In other words, == is preferred whenever possible, to ensure consistent behavior.
While this is probably more of a "technicality" and is "obvious", you can always use matches! even when the inner value doesn't implement PartialEq; but more importantly, I'd argue that one should only use matches!if the underlying enum doesn't implement PartialEq or you don't care about disagreeing with the "canonical" partial equivalence relation (i.e., you should not rely on matches! in lieu of == when it exists and you want it to "agree"). By relying on matches!, you run the risk of not implementing the same partial equivalence relation. Even if PartialEq is not implemented, you run the risk of a non-major breaking change to the library that adds a PartialEq implementation that doesn't agree with the matches! in your code. So while I understand the "essence" of what you're saying, it's not entirely without risk.
use foo::Foo;
fn foo_eq(x: &Foo, y: &Foo) -> bool {
// Probably OK when using `foo 1.0.0`; but when the purpose
// of `foo_eq` is to be used as _the_ partial equivalence
// relation for `Foo`, then this will disagree with what is
// typically considered the canonical partial equivalence
// relation (i.e., `Foo::eq`).
match *x {
Foo::First => matches!(*y, Foo::First),
Foo::Second => matches!(*y, Foo::Second),
}
}
Certainly, so here is the amended statement (emphasis added where the edit exists):
I'd argue that one should only use matches!if the underlying enum doesn't implement PartialEqor you don't care about disagreeing with the "canonical" partial equivalence relation (i.e., you should not rely on matches! in lieu of == when it exists).