高阶函数

高阶函数

高阶函数与普通函数的不同在于,它可以使用一个或多个函数作为参数,可以将函数作为返回值。Rust 的函数是 first class type,所以支持高阶函数。

函数类型

关键字 fn 可以用来定义函数。除此以外,它还用来构造函数类型。与函数定义主要的不同是,构造函数类型不需要函数名、参数名和函数体。在 Rust Reference 中的描述如下:

The function type constructor fn forms new function types. A function type consists of a possibly-empty set of function-type modifiers (such as unsafe or extern), a sequence of input types and an output type.

fn inc(n: i32) -> i32 {//函数定义
  n + 1
}

type IncType = fn(i32) -> i32;//函数类型

fn main() {
  let func: IncType = inc;
  println!("3 + 1 = {}", func(3));
}

上例首先使用fn定义了inc函数,它有一个i32类型参数,返回i32类型的值。然后再用fn定义了一个函数类型,这个函数类型有 i32 类型的参数和 i32 类型的返回值,并用type关键字定义了它的别名IncType。在main函数中定义了一个变量func,其类型就为IncType,并赋值为inc,然后在pirntln宏中调用:func(3)。可以看到,inc函数的类型其实就是IncType。这里有一个问题,我们将inc赋值给了func,而不是&inc,这样是将inc函数的拥有权转给了func吗,赋值后还可以以inc()形式调用inc函数吗?先来看一个例子:

fn main() {
  let func: IncType = inc;
  println!("3 + 1 = {}", func(3));
  println!("3 + 1 = {}", inc(3));
}

type IncType = fn(i32) -> i32;

fn inc(n: i32) -> i32 {
  n + 1
}

这说明,赋值时,inc 函数的所有权并没有被转移到 func 变量上,而是更像不可变引用。在 rust 中,函数的所有权是不能转移的,我们给函数类型的变量赋值时,赋给的一般是函数的指针,所以 rust 中的函数类型,就像是 C/C++中的函数指针,当然,rust 的函数类型更安全。可见,rust 的函数类型,其实应该是属于指针类型(Pointer Type)。rust 的 Pointer Type 有两种,一种为引用(Reference &),另一种为原始指针(Raw pointer *)。

函数作为参数

函数作为参数,其声明与普通参数一样。看下例:

fn main() {
  println!("3 + 1 = {}", process(3, inc));
  println!("3 - 1 = {}", process(3, dec));
}

fn inc(n: i32) -> i32 {
  n + 1
}

fn dec(n: i32) -> i32 {
  n - 1
}

fn process(n: i32, func: fn(i32) -> i32) -> i32 {
  func(n)
}

例子中,process就是一个高阶函数,它有两个参数,一个类型为i32n,另一个类型为fn(i32)->i32的函数func,返回一个i32类型的参数;它在函数体内以n作为参数调用func函数,返回func函数的返回值。运行可以得到以下结果:

3 + 1 = 4
3 - 1 = 2

不过,这不是函数作为参数的唯一声明方法,使用泛型函数配合特质(trait)也是可以的,因为 rust 的函数都会实现一个trait:FnOnceFnFnMut。将上例中的process函数定义换成以下形式是等价的:

fn process<F>(n: i32, func: F) -> i32
    where F: Fn(i32) -> i32 {
    func(n)
}

允许把闭包作为参数来生成新的函数:

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

fn apply<F>(f: F, y: i32) -> i32
    where F: Fn(i32) -> i32
{
    f(y) * y
}

fn factory(x: i32) -> Box<Fn(i32) -> i32> {
    Box::new(move |y| x + y)
}

fn main() {
    let transform: fn(i32) -> i32 = add_one;
    let f0 = add_one(2i32) * 2;
    let f1 = apply(add_one, 2);
    let f2 = apply(transform, 2);
    println!("{}, {}, {}", f0, f1, f2);

    let closure = |x: i32| x + 1;
    let c0 = closure(2i32) * 2;
    let c1 = apply(closure, 2);
    let c2 = apply(|x| x + 1, 2);
    println!("{}, {}, {}", c0, c1, c2);

    let box_fn = factory(1i32);
    let b0 = box_fn(2i32) * 2;
    let b1 = (*box_fn)(2i32) * 2;
    let b2 = (&box_fn)(2i32) * 2;
    println!("{}, {}, {}", b0, b1, b2);

    let add_num = &(*box_fn);
    let translate: &Fn(i32) -> i32 = add_num;
    let z0 = add_num(2i32) * 2;
    let z1 = apply(add_num, 2);
    let z2 = apply(translate, 2);
    println!("{}, {}, {}", z0, z1, z2);
}

函数作为返回值

函数作为返回值,其声明与普通函数的返回值类型声明一样。看例子:

fn main() {
   let a = [1,2,3,4,5,6,7];
   let mut b = Vec::<i32>::new();
   for i in &a {
       b.push(get_func(*i)(*i));
   }
   println!("{:?}", b);
}

fn get_func(n: i32) -> fn(i32) -> i32 {
    fn inc(n: i32) -> i32 {
        n + 1
    }
    fn dec(n: i32) -> i32 {
        n - 1
    }
    if n % 2 == 0 {
        inc
    } else {
        dec
    }
}

例子中的高阶函数为get_func,它接收一个 i32 类型的参数,返回一个类型为fn(i32) -> i32的函数,若传入的参数为偶数,返回inc,否则返回dec。这里需要注意的是,inc函数和dec函数都定义在get_func内。在函数内定义函数在很多其他语言中是不支持的,不过 rust 支持,这也是 rust 灵活和强大的一个体现。不过,在函数中定义的函数,不能包含函数中(环境中)的变量,若要包含,应该闭包(详看 13 章 闭包)。所以下例:

fn main() {
  let f = get_func();
  println!("{}", f(3));
}

fn get_func() -> fn(i32)->i32 {
  let a = 1;
  fn inc(n:i32) -> i32 {
    n + a
  }
  inc
}

// 会抛出异常
上一页
下一页