写给前端看的Rust教程(15)闭包

·  阅读 3280
写给前端看的Rust教程(15)闭包

原文:24 days from node.js to Rust

前言

JavaScript中闭包是如此的常见以至于你很难想象如果没有闭包该如何在JavaScript中写代码,Rust中的闭包与JavaScript中的类似,大部分让你感觉舒适的内容都被保留

注意:闭包的定义是指一个函数保持了对外部状态的引用,在本文中,我们所谈到的“闭包”指的是匿名函数,无论是它是否引用了外部的变量

正文

闭包语法

下面会把JavaScript/TypeScript的闭包与Rust语法相比较

基础语法

TypeScript版:

let closure = () => {
  console.log("Hi! I'm in a closure");
};
closure();
复制代码

Rust版:

let closure = || {
  println!("Hi! I'm in a closure");
};
closure();
复制代码

输出:

Hi! I'm in a closure
复制代码

Rust使用竖线代替了括号,并且参数和函数体之间无分隔

简洁语法

对于只包含一个函数体的,无论是JavaScript/TypeScript还是Rust,我们都可以省略花括号{},进行简写

TypeScript版:

let double = (num: number) => num + num;
let num = 4;
console.log(`${num} + ${num} = ${double(num)}`);
复制代码

Rust版:

let double = |num: i64| num + num;
let num = 4;
println!("{} + {} = {}", num, num, double(num));
复制代码

输出:

4 + 4 = 8
复制代码

外部变量

一个真正的闭包是要从父作用域引用变量的,这在Rust也是支持的

TypeScript版:

let name = "Rebecca";
closure = () => {
  console.log(`Hi, ${name}.`);
};
closure();
复制代码

Rust版:

let name = "Rebecca";
let closure = || {
  println!("Hi, {}.", name);
};
closure();
复制代码

输出:

Hi, Rebecca.
复制代码

需要注意的是,如果需要在闭包中改变外部变量值,则闭包需要也是可变的

TypeScript版:

let counter = 0;
closure = () => {
  counter += 1;
  console.log(`This closure has a counter. I've been run ${counter} times.`);
};
closure();
closure();
closure();
console.log(`The closure was called a total of ${counter} times`);
复制代码

Rust版:

let mut counter = 0;

let mut closure = || {
  counter += 1;
  println!(
    "This closure has a counter. I've been run {} times.",
    counter
  );
};
closure();
closure();
closure();
println!("The closure was called a total of {} times", counter);
复制代码

输出:

This closure has a counter. I've been run 1 times.
This closure has a counter. I've been run 2 times.
This closure has a counter. I've been run 3 times.
The closure was called a total of 3 times
复制代码

返回闭包

动态的生成一个闭包也是很简单的,例如我们创建一个make-adder函数,它接受个参数,返回一个闭包,该闭包继续接受一个参数并与第一个参数相加

TypeScript版:

nction makeAdder(left: number): (left: number) => number {
  return (right: number) => {
    console.log(`${left} + ${right} is ${left + right}`);
    return left + right;
  };
}

let plusTwo = makeAdder(2);
plusTwo(23);
复制代码

Rust版:

fn make_adder(left: i32) -> impl Fn(i32) -> i32 {
  move |right: i32| {
    println!("{} + {} is {}", left, right, left + right);
    left + right
  }
}

let plus_two = make_adder(2);
plus_two(23);
复制代码

输出:

2 + 23 is 25
复制代码

Fn,FnMut,FnOnce

闭包有三种类型:

  1. Fn:函数不可变的借用包含的所有变量
  2. FnMut:函数可变的借用包含的所有变量
  3. FnOnce:函数丢失了变量的所有权,只能返回一次,例如:
    let name = "Dwayne".to_owned();
    let consuming_closure = || name.into_bytes();
    let bytes = consuming_closure();
    let bytes = consuming_closure(); // This is a compilation error
    复制代码

move关键字

move关键字表明块或闭包会获取它所引用的变量的所有权,在上面的例子里move关键字是很必要的,因为我们返回了一个闭包,该闭包有left变量的引用并且函数结束后按理说变量的生命周期就结束了,如果不加move关键字则返回闭包就会出现问题

compose函数

compose函数以两个函数为参数,返回一个闭包来流水线化运行两个函数

TypeScript版:

function compose<T>(f: (left: T) => T, g: (left: T) => T): (left: T) => T {
  return (right: T) => f(g(right));
}

let plusTwo = makeAdder(2); // ← makeAdder from above
let timesTwo = (i: number) => i * 2;
let doublePlusTwo = compose(plusTwo, timesTwo);
console.log(`${10} * 2 + 2 = ${doublePlusTwo(10)}`);
复制代码

Rust版:

fn compose<T>(f: impl Fn(T) -> T, g: impl Fn(T) -> T) -> impl Fn(T) -> T {
  move |i: T| f(g(i))
}

let plus_two = make_adder(2);  // ← make_adder from above
let times_two = |i: i32| i * 2;
let double_plus_two = compose(plus_two, times_two);
println!("{} * 2 + 2 = {}", 10, double_plus_two(10));
复制代码

输出:

10 * 2 + 2 = 22
复制代码

函数引用

这部分内容将会介绍在Rust如何将闭包以一等公民来使用

TypeScript版:

function regularFunction() {
  console.log("I'm a regular function");
}

let fnRef = regularFunction;
fnRef();
复制代码

Rust版:

fn regular_function() {
  println!("I'm a regular function");
}

let fn_ref = regular_function;
fn_ref();
复制代码

输出:

I'm a regular function
复制代码

闭包存储

鉴于Fn* traitsdyn [trait]的差异,将函数存储起来是个比较取巧的事情,下面的代码展示了创建一个classstruct,可以用闭包来实例化,你可以通过调用.run()从实例中执行存储的闭包

TypeScript版:

class DynamicBehavior<T> {
  closure: (num: T) => T;
  constructor(closure: (num: T) => T) {
    this.closure = closure;
  }
  run(arg: T): T {
    return this.closure(arg);
  }
}

let square = new DynamicBehavior((num: number) => num * num);
console.log(`${5} squared is ${square.run(5)}`);
复制代码

Rust版:

struct DynamicBehavior<T> {
  closure: Box<dyn Fn(T) -> T>,
}

impl<T> DynamicBehavior<T> {
  fn new(closure: Box<dyn Fn(T) -> T>) -> Self {
    Self { closure }
  }
  fn run(&self, arg: T) -> T {
    (self.closure)(arg)
  }
}

let square = DynamicBehavior::new(Box::new(|num: i64| num * num));
println!("{} squared is {}", 5, square.run(5))
复制代码

输出:

I'm a regular function
复制代码

注意:我们不能在函数参数或返回值之外使用impl [trait],所以如果想存储一个闭包的话,我们需要将其以dyn [trait]来存储,你还需要记住dyn [trait]unsized的,Rust并不喜欢,我们可以用Box来解决,具体可见 教程14

相关阅读

总结

Rust的闭包没那么复杂,不过在遇到异步的时候确实会有些棘手,在下一篇我们会介绍Rust的借用检查机制

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改