前言
在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
闭包有三种类型:
Fn
:函数不可变的借用包含的所有变量FnMut
:函数可变的借用包含的所有变量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* traits
和dyn [trait]
的差异,将函数存储起来是个比较取巧的事情,下面的代码展示了创建一个class
或struct
,可以用闭包来实例化,你可以通过调用.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
相关阅读
- The Rust Book: ch 13.01 - Closures
- The Rust Book: ch 19.05 - Advanced Functions and Closures
- Rust by Example: Closures
- Rust Reference: Closure expressions
总结
Rust
的闭包没那么复杂,不过在遇到异步的时候确实会有些棘手,在下一篇我们会介绍Rust
的借用检查机制