Also note that let _ = …
does not really mean “drop immediately” in all circumstances. It means “drop immediately” if the right hand side is a value expression; but if it’s a place expression, it doesn’t really do anything. So the effect on drop timing can be the opposite as well. E.g.
let long_lived = String::from(…);
{
// short block
let _inner = long_lived;
// drops _inner
}
// now long_lived is gone, and its value already dropped
exec_other_code(); // runs after destructor call on the value long_lived used to have
vs
let long_lived = String::from(…);
{
// short block
let _ = long_lived; // binds nothing, no-op
// no drops happen
}
// now long_lived is still initialized, and its value not dropped
exec_other_code(); // runs before destructor call on the value long_lived has
So really, the meaning of _
patterns is consistently the same, but it simply isn’t “drop immediately” but rather “don’t move the value in the first place”. For value-expressions, this means the value stays in a temporary and is dropped “immediately” for let
, after all, because those temporaries in a let
are scoped only to the (evaluation of the) let
statement itself. For function parameters, the question of “how long does the function parameter (or parts of it live) if not moved into an explicitly named function parameter” is answered by looking into the reference…
here is the most relevant section – ah! – and it even does provide the example for “case where _
does effect relative drop order between parts of function parameters, for _
in a larger overall pattern”!
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("drop({})", self.0);
}
}
// Drops `y`, then the second parameter, then `x`, then the first parameter
fn patterns_in_parameters(
(x, _): (PrintOnDrop, PrintOnDrop),
(_, y): (PrintOnDrop, PrintOnDrop),
) {}
fn main() {
// drop order is 3 2 0 1
patterns_in_parameters(
(PrintOnDrop("0"), PrintOnDrop("1")),
(PrintOnDrop("2"), PrintOnDrop("3")),
);
}
So I guess technically we could argue that when writing |_foo|
instead of |_|
that does have the effect of introducing the binding/variable _foo
, which is dropped immediately before the implicit actual/original “function parameter” itself is being dropped (which then is a no-op, because it had been moved out of); whereas |_|
does not create such a binding and at the end of the function just the implicit place logically holding the “function parameter” itself is dropped. Just those 2 behaviors are effectively the same [and will always be the same if matching the whole function argument against a single binding pattern or _
pattern].