闭包

闭包是一种匿名函数,它可以赋值给变量,也可以作为参数传递给其它函数。不同于函数的是,它允许捕获调用者作用域中的值,如:

fn main() {
    let x = 1;
    let sum = |y| x + y;
    assert_eq!(3, sum(2));
}

在上述代码中,闭包sum使用了作用域外的值x

与函数相比,由于闭包不会暴露给外界调用,因此在绝大多数情况下,在闭包中,无需指定传入参数与传出参数的类型,编译器会自行推断。

闭包格式:

|param1, param2, ...| {
    语句1;
    语句2;
    返回表达式
}

闭包的类型签名:

struct Cacher<T>
where 
    T: Fn(u32) -> u32,
{
    query: T,
    value: Option<u32>,
}

T: Fn(u32) -> u32表示 T类型必须实现一个闭包特征。该闭包有一个u32参数,返回一个u32类型的值。

闭包对内存的影响

当闭包从环境中捕获一个值时,会分配内存去存储这些值。对于有些场景来说,这种额外的内存分配会成为一种负担。与之相比,函数就不会去捕获这些环境值,因此定义和使用函数不会拥有这种内存负担。

三种 Fn 特征

闭包捕获变量有三种途径,恰好对应函数参数的三种转入方式:转移所有权、可变借用、不可变借用。

FnOnce

对应转移所有权,该闭包只能运行一次:

fn fn_once<F>(func: F)
where 
    F: FnOnce(usize) -> bool,
{
    println!("{}", func(3));
    println!("{}", func(4));
}
 
fn main() {
    let x = vec![1, 2, 3];
    fn_once(|z|{z == x.len()})
}

所有的闭包都自动实现了 FnOnce 特征。

FnMut

对应可变借用:

fn main() {
    let mut s = String::new();
    let mut update_string = |str| s.push_str(str);
    exec(update_string);
    println("{:?}", s);
}
 
fn exec<'a, F: FnMut(&'a str)>(mut f: F) {
    f("hello")
}

没有移出所捕获变量的所有权的闭包自动实现了 FnMut 特征。

Fn

对应不可变借用:

fn main() {
    let s = "hello, ".to_string();
    let update_string = |str| println!("{},{}", s, str);
    exec(update_string);
    println!("{:?}", s);
}
 
fn exec<'a, F: Fn(String) -> ()>(f: F) {
    f("world".to_string())
}

不需要对捕获变量进行改变的闭包自动实现了 Fn 特征。

move

move关键字可以强制闭包取得捕获变量的所有权。这种用法通常用于闭包的生命周期大于捕获变量的生命周期时,例如将闭包返回或移入其他线程。

fn main() {
    let s = String::new();
    let update_string = move || println!("{}", s);
    exec(update_string);
}
 
fn exec<F: FnOnce()>(f: F) {
    f()
}
  • Fn决定闭包如何使用被捕获的变量
  • move决定如何捕获变量

在 Rust 中,闭包的行为取决于它所捕获的变量。如果闭包捕获的是一个可复制类型(如 String),则它会对其进行复制,而不是移动。

这是因为闭包需要对其捕获的变量进行所有权管理,以确保在闭包执行期间它们仍然有效。对于可复制类型,复制是最简单的管理方式。

需要注意的是,如果想要在闭包中移动一个值,可以使用 move 关键字来强制将其移动到闭包中。在这种情况下,闭包将获取变量的所有权,并且该变量将不再可用于闭包之外。

闭包作为函数返回值

将闭包作为函数返回值时,可以返回一个特征对象(要求实现该闭包特征)。

fn main() {
    let f = factory(2);
    let answer = f(2);
    assert_eq!(7, answer);
    println!("Hello, world!");
}
 
fn factory(x: i32) -> Box<dyn Fn(i32) -> i32> {
    let num = 5;
    if x > 1{
        Box::new(move |x| x + num)
    } else {
        Box::new(move |x| x - num)
    }
}