核心:闭包是什么,闭包是能控制环境变量的特殊函数。正因为此,环境变量被闭包捕获了,原作用域下的环境变量的所有权就没了,完全由闭包控制。当然,个别情况不一样,下面会讲到。但实质就是这样。
这个是理解闭包的关键。也是理解move的关键。
闭包对进入其中的自由变量而言,有点象黑洞。自由变量进去了,很难再逃脱了。除非,有特别的力量。move,你该上场了,开始你的表演…
let f = | j:i32 | i =i+j ; // j为输入参数,i为环境变量 等价于 =>Fn(j:i32)->()
let g = || a+b ; // a,b为环境变量,无输入参数 ,等价于 =>Fn() ->T
一、copy trait 下move
我们知道,象i32,i64,等实现了copy trait。在赋值等行为是会自动copy一份。
move在闭包中的作用是,可以强制获取环境变量的所有权:
情景1 :有move, 作用域相同
let mut num1 = 5;
let mut f1 = move |x: i32| num1 = x + num1;
let data1 = f1(2_i32);
println!("num1:{:?} data1:{:?}", num1, data1); //num1:5 data1:()
结论:有move下,自由变量可以穿越闭包函数。
情景2:无move,作用域相同
let mut num2 = 5;
let mut f2 = |x: i32| num2 = x + num2;
let data2 = f2(2_i32);
println!("num2:{:?} data2:{:?}", num2, data2); //num2:7 data2:()
结论:无move下,自由变量num2无影响.
情景3:无move,作用域不同
let mut num = 5;
{
let mut add_num = |x: i32| num += x;
add_num(5);
}
assert_eq!(10, num);
情景4:有move,作用域不同
如果我们换成一个 move 闭包,就会出现不同:
let mut num = 5;
{
let mut add_num = move |x: i32| num += x;
add_num(5);
}
assert_eq!(5, num);
我们只得到 5。而不是从 num 得到可变的 borrow 我们对副本拥有所有权。
总结一下:
在实现了copy trait的自由变量,在move进一份copy,并拥有copy的所有权。原来变量不变。
二、no copy trait下的move
对于没有实现copy trait的String类型的环境变量,情况会如何?
1、有move, 作用域相同
let mut str2 = "julia book".to_string();
let str2_0 = "i love it";
let mut f4 = move |x: &str| str2 = str2 + x + str2_0;
let _str2 = f4(" 2013");
println!("str2_0:{:?}", str2_0);//无影响
//println!("str2:{:?}", str2); //=> error: str2也已经被借用了
在有move的情况下,因为没有实现copy,那只好把这个变量的真身借走。外部就没有变量了。
2、无move,作用域相同
let mut str2 = "julia book".to_string();
let str2_0 = "i love it";
let mut f4 = |x: &str| str2 = str2 + x + str2_0;
let _str2 = f4(" 2013");
println!("str2_0:{:?}", str2_0); //不变化
//println!("str2:{:?}", str2); //=> error: str2也已经被借用了
以上1、2两种情况,有、无move情况相同,对不可变自由变理无影响,但对可变变量有影响;
3、有move,作用域不相同
let mut mybook = "rust".to_string();
let comment: &str = "ok?";
{
let f = move |x: &str| {
mybook = mybook + x + comment;
println!("move=> mybook:{:?}", mybook);
};
f("primer is a good book!");
};
println!("comment:{:?}", comment);//=> comment仍可用
//println!("mybook:{:?}", mybook); //=> 报错,mybook已经被f move走了,不存在
4、 无move,作用域不相同
let mut mybook = "rust".to_string();
let comment: &str = "ok?";
{
let f = |x: &str| {
mybook = mybook + x + comment;
println!("无move=> mybook:{:?}", mybook);
};
f("primer is a good book!");
};
println!("comment:{:?}", comment); //comment仍可用
//println!("mybook:{:?}", mybook); //=> 报错,mybook已经被f move走了,不存在
以上3、4两种情况,也是一样,move无力回天。
总结一下,在没有实现copy trait的情况下,不管有无move:
(1)可变的自由变量进入闭包后,都无法穿越过闭包;//如 mybook
(2)不可变的自由变量是没有问题的。//如:comment
三、为什么要有move
想一想,如果没有move,会如何?
use std::thread;
fn main() {
let x = 1;
thread::spawn(move || {
println!("x is {}", x);
});
}
因此,你在thread::spawn中,经常会碰到move.
如果没有move,子线程则无法捕捉x变量。
四、其它情况
fn main() {
let x = "hello";
thread::spawn(move || {
let y = String::from("hi,") + x;
println!("y is {}", y);
});
println!("....x:{:?}", x);
thread::sleep_ms(500000);
}
输出:
....x:"hello"
y is hi,hello
五、具体的细节
关于地址:
1、有move
fn main() {
let sy_time0 = SystemTime::now();
let mut s: String = "rust".into();
let mut n: i32 = 10;
println!("before => s: {} address: {:p}", s, s.as_ptr());
println!("before => n: {} address: {:p}", n, &n);
println!("move=>");
let mut c = move || {
s += "love";
n += 5;
println!("move => s: {}, address: {:p}", s, s.as_ptr());
println!("move => n: {}, address: {:p}", n, &n);
};
c();
c();
c();
println!("after closure => check var status: ");
println!("after closure => n: {} address: {:p}", n, &n); // 因为有 copy
//println!("after closure => s: {} ", s); // 已经没有所有权了
thread::sleep_ms(500000);
}
在有move的情况下,n因为实现了copy,原值还在。但s却是所有权没了。
2、无move
fn main() {
let sy_time0 = SystemTime::now();
let mut s: String = "rust".into();
let mut n: i32 = 10;
println!("before => s: {} address: {:p}", s, s.as_ptr());
println!("before => n: {} address: {:p}", n, &n);
println!("no move=>");
let mut c = || {
s += "love";
n += 5;
println!("move => s: {}, address: {:p}", s, s.as_ptr());
println!("move => n: {}, adress: {:p}", n, &n);
};
c();
c();
c();
println!("after closure => check var status: ");
//println!("after closure => n: {} ", n); // 已经没有所有权了
//println!("after closure => s: {} ", s); // 已经没有所有权了
thread::sleep_ms(500000);
}
3、有move和无move下比较
比较:
move => 有实现copy trait的变量,是副本进了closure; 真身并没有影响;没有实现copy trait 所有权无。
无move =>;所有权全部没了。
理解的角度:
另个,从另外一个角度也可以看到,当闭包不断运行时,即c(), 函数中控制了相关的环境变量了。
只是copy trait 让副本的进去的,才能逃出闭包的魔掌。因为是副本,所以地址自然就不一样了。
特明,本文中有一部分参考了相关部分内容:http://wiki.jikexueyuan.com/project/rust/closures.html