在Rust,生命周期和所有权一个非常重要的实现。在闭包中,一般变量就像是小天体掠过大天体时被捕获,再也无法离开。那么要想离开怎么办?只有继续提供能量,能冲出大天体的束缚。你可以想象一下,有一个上帝之手,轻推了一下一颗小天体(比如流浪地球中的地球),然后整个小天体就犹如一个子弹飞出了大天体的范围,向着未知继续前进。
那么在Rust的闭包里,提供这个能量的是什么?是Move这个关键字。看一个例子:
let mut n = 8;
let n1 = |x: i32| n = x + n;
let y = &mut n;
这在前面的讲过,会报一个错误,那么加一个MOVE呢:
let mut n = 8;
let n1 =move |x: i32| n = x + n;
let y = &mut n;
在前面其实也简单总结过,无move对于实现copy trait的变量,则进入闭包等于所有权的消失。反之,则是是Copy了一个副本。这个关键字大多线程经常遇到,比如在前面的客户端的代码的线程中就有这个move。而对于没有实现实现copy trait的变量,有move的情况下,仍然被借走了。
let mut s0 = "s0test".to_string();
let s1 = "gogogo";
let mut f0 = move |x: &str| s0 = s0 + x + s1;
let s3 = f0("my test is:");
println!("s1:{:?}", s1);//OK
println!("s0:{:?}", s0); //=> err: s0也已经被借用了
这个所有权,开始学的时候儿一定会虐得够呛,不过不要急,这玩意儿没啥。
闭包是如何实现的呢?Rust的闭包和其它语言略有不同,它主要是trait的语法糖实现的。和c++重载“()”小括号运算符有很大的类似。看一下代码实现(代码出自rustprimer):
# mod foo {
pub trait Fn : FnMut {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
pub trait FnMut : FnOnce {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
pub trait FnOnce {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
# }
这就是Rust闭包的|| {}语法实现的三个trait 的语法糖。Rust 将自动创建一个结构体,而impl适配 trait,从而达到应用的目的。
在前面讲函数时说过,函数可以做为参数也可以做为返回值,那么类似于函数的这个闭包是不是也可以呢?答案是必须滴。看一下子例程:
fn factory() -> Box i32> {
let num = 5;
Box::new(move |x| x + num)
}
# fn main() {
let f = factory();
let answer = f(1);
assert_eq!(6, answer);
# }
这里面使用了Box,也是Rust“无敌”的一种表示。
而且闭包还可以当成函数指针一样应用:
fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
some_closure(1)
}
fn add_one(i: i32) -> i32 {
i + 1
}
let f = add_one;
let answer = call_with_one(&f);
assert_eq!(2, answer);
闭包做为参数:
fn call_with_one(some_closure: F) -> i32
where F : Fn(i32) -> i32 {
some_closure(1)
}
let answer = call_with_one(|x| x + 2);
assert_eq!(3, answer);
以上代码出自《RustPrimer》
在网上一般都说Rust这玩意儿是用来打击c++的,越看越像。确实是比较像。这用法和c++的用法真是高度相似啊。没人关心你怎么实现的,大家只关心你们出来啥效果,抓住老鼠的都是好猫。
每一个技术的演进,都是不断在吸收外人和自己本身的技术的缺点和缺少的优秀点之上不断迭代的,包括rust,所以这给我们一个经验,做开发,不要总妄想一蹴而就,还是在版本迭代的基础上不断的前进,越走越好才是一道!
努力吧,归来的少年!