Wang's blog

四、函数与闭包

Published on

函数

函数使用fn关键字声明,如有需要,则指定参数名称与类型,以及返回值的类型。返回方式除了可以使用return外,还可以在最后一条语句给出一个表达式。

fn add_one(x: i32) -> i32 {
    x + 1
}

语句与表达式

  • Rust程序由语句组成,常见的语句有let语句与表达式语句
  • 代码块也是表达式,它的最后一个表达式被返回。需要注意的是,如果最后一个表达式以分号结尾,则返回值为()

函数指针

函数指针的类型是fn,在使用时还需要指定输入输出类型。

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

// 将add_one函数指针作为do_twice的参数
let answer = do_twice(add_one, 5);

函数指针fn是一个类型,所以在作为参数时可以直接使用,而无需使用泛型并使用闭包特性进行限制。此外,fn实现了全部3种闭包特性(Fn,FnMut与FnOnce),这意味着可以将函数指针传给所有将闭包作为参数的函数。因此,在写函数时最好使用泛型与闭包特性,这样函数与闭包都可以作为参数。

但是,在与外部代码交互时,可能只接收函数指针作为参数。

闭包

闭包是可以捕获环境中变量的函数,相当于lambda表达式。

// 一个捕获了变量x的闭包
|val| val + x

闭包的特点有:

  • 使用||代替()
  • 如果只有一条语句可以省略{}
  • 可以捕获环境中的变量

捕获变量

闭包可以捕获环境中的变量。可以通过以下3种方式:

  • 引用:&T
  • 可变引用:&mut T
  • 值:T
// 引用捕获
let color = String::from("green");
let print = || println!("`color`: {}", color);
print();

// 可变引用捕获
let mut count = 0;
let mut inc = || {
    count += 1;
    println!("`count`: {}", count);
};
inc();

// 值捕获
let movable = Box::new(3);
let consume = || {
    println!("`movable`: {:?}", movable);
    mem::drop(movable);
};
consume();

// 使用move强制转移所有权
let haystack = vec![1, 2, 3];
let contains = move |needle| haystack.contains(needle);
println!("{}", contains(&1));

类型匿名性

每个闭包在定义时,编译器会为它声明一个匿名结构体类型以保存捕获的变量,并根据其行为为该类型实现以下3个特性之一:

  • Fn:使用引用捕获
  • FnMut:使用可变引用捕获
  • FnOnce:使用值捕获

类似函数指针,也可以声明一个指定输入输出类型的闭包类型,如Fn(i32) -> i32,它实际上是一个更加具体的闭包特性。

作为输入参数

在将闭包作为函数的输入输出时,需要显式说明其类型,然而闭包的具体类型是无法获取的。此时可以将以上3个特性作为特性范围限制,编写泛型函数。如果知道一个闭包的具体输入输出类型,也可以使用具体的闭包特性作为参数。

// 将一个值捕获闭包作为参数
fn apply<F>(f: F) where
    F: FnOnce() {
    f();
}

// 将一个知道输入输出类型的闭包作为参数
fn apply_to_3<F>(f: F) -> i32 where
    F: Fn(i32) -> i32 {
    f(3)
}

作为输出参数

使用方法与作为输入参数类似,可以使用impl语法糖。需要注意闭包作为输出参数时必须使用move,因为捕获的引用变量会在函数返回后释放。

fn create_fn() -> impl Fn() {
    let text = "Fn".to_owned();
    move || println!("This is a: {}", text)
}

fn create_fnmut() -> impl FnMut() {
    let text = "FnMut".to_owned();
    move || println!("This is a: {}", text)
}

fn create_fnonce() -> impl FnOnce() {
    let text = "FnOnce".to_owned();
    move || println!("This is a: {}", text)
}

返回动态类型的闭包

上述方法声明的函数是静态泛型函数,实际上的输入输出类型是固定的。与普通特性类似,可以使用Box<dyn Fn>语法由一个非泛型函数返回位于堆上的不同类型的闭包。

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

高阶函数

使用其它函数(或闭包)作为参数的函数。

发散函数

!是一种特殊的永不返回类型,将其作为返回值的函数称为发散函数。永不返回类型与空类型()不同,它可以用在需要任何类型的地方。

fn foo() -> ! {
    panic!("This call never returns.");
}