Rust : 闭包、move 与自由变量的穿越

核心:闭包是什么,闭包是能控制环境变量的特殊函数。正因为此,环境变量被闭包捕获了,原作用域下的环境变量的所有权就没了,完全由闭包控制。当然,个别情况不一样,下面会讲到。但实质就是这样。
这个是理解闭包的关键。也是理解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);
}

在这里插入图片描述
在没有move的情况下,n和s所有权都没了。

3、有move和无move下比较
比较:
move => 有实现copy trait的变量,是副本进了closure; 真身并没有影响;没有实现copy trait 所有权无。
无move =>;所有权全部没了。

理解的角度:
另个,从另外一个角度也可以看到,当闭包不断运行时,即c(), 函数中控制了相关的环境变量了。
只是copy trait 让副本的进去的,才能逃出闭包的魔掌。因为是副本,所以地址自然就不一样了。

特明,本文中有一部分参考了相关部分内容:http://wiki.jikexueyuan.com/project/rust/closures.html

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页