@leodasvacas
A non-Copy value is moved if used as a value in a closure, so standard copy/move semantics there. But closures capture Copy types by reference when used as a value in the closure, unlike standard copy/move semantics
The way I see it, that Copy behavior is following normal copy semantics: it copies when used by value and no sooner. The non-Copy case is actually the odd one because it eagerly moves into the closure and drops the non-Copy value at the end of the closure. However, this difference can only really be observed on Drop. That is, you’ll get an eager drop at the end of the closure if the value could have been used by value in the closure. To illustrate, given:
struct DebugDrop(&'static str);
impl Drop for DebugDrop {
fn drop(&mut self) {
println!("{}", self.0);
}
}
Compare closure drop semantics:
fn main() {
let a = DebugDrop("dropping a");
let maybe_drop = || {
if false {
drop(a);
}
};
maybe_drop(); // "dropping a" printed (eager drop)
println!("end"); // "end" printed.
}
With normal drop semantics:
fn main() {
let a = DebugDrop("dropping a");
if false {
drop(a);
} // an eager drop would print "dropping a" here (but rust doesn't do that).
println!("end"); // "end" printed.
// "dropping a" printed (normal drop)
}
So you’re right, the current behavior of closures does (subtly) deviate from copy semantics. However, I’d argue that it doesn’t do so in a way that forces the programmer to remember “this type is copy, this type isn’t” (any more than with usual control flow).
My concern with switching on Copy in this case is that, if you did, the following cases would behave wildly differently:
let mut number = Some(1);
match &mut number {
Some(num) => {
num: u32;
},
None => {},
}
let mut string = Some(String::new());
match &mut string {
Some(s) => {
s: &mut String;
},
None => {},
}
In closures, it would be s: String (and s would be moved).
FYI, I believe your example is technically unsound as you’re mutably aliasing a shared reference. You can achieve something similar with a Cell (but not quite the same because Cell isn’t copy).
use std::cell::Cell;
fn main() {
let mut x = Cell::new(1);
// Copy semantics say x is copied here, so get_x should always be 1.
let get_x = || x.get();
x.set(2);
println!("{}", get_x()); // And yet this prints '2'.
}
IMO, a better way to demonstrate this is by mutating from within the closure:
fn main() {
let mut x = 1;
{
let mut get_x = || {
x += 1;
x
};
println!("{}", get_x()); // 2
}
println!("{}", x); // 2
}
@ker
IntelliJ does something similar to rewrite non-idiomatic Kotlin to idiomatic Kotlin and I think this is a great way to teach people a language (tooling that helps programmers write code).