Beginng_Rust(译):定义闭包(第十三章)

在本章中,您将学习:

•为什么需要匿名内联函数,对参数和返回值类型进行类型推断,而不必编写大括号,并且可以访问函数定义点处的活动变量
•如何声明和调用名为“闭包”的轻量级函数

需要“一次性”函数

以升序对数组进行排序的Rust方法如下:

let mut arr = [4, 8, 1, 10, 0, 45, 12, 7];
arr.sort();
print!("{:?}", arr);

这将打印:“[0,1,4,7,8,10,12,45]”;

但是如果你想按降序排序,或者使用其他一些标准,那么就没有预先打包的功能; 你必须调用sort_by函数,向它传递对比较函数的引用。 这样的函数接收两个项目,并返回一个指示,表明哪个项目必须先于另一个项目:

let mut arr = [4, 8, 1, 10, 0, 45, 12, 7];
use std::cmp::Ordering;
fn desc(a: &i32, b: &i32) -> Ordering {
if a < b { Ordering::Greater }

else if a > b { Ordering::Less }
else { Ordering::Equal }
}
arr.sort_by(desc);
print!("{:?}", arr);

这将打印:“[45,12,10,5,7,4,1,0]”。

desc函数返回一个值,其类型由标准库以下列方式定义:

enum Ordering { Less, Equal, Greater }

这有效,但它有几个缺点。
首先,desc函数定义为仅在一个点中使用,在以下语句中。 通常不会创建仅在一个点中使用的函数; 相反,函数的主体在需要的地方扩展。 但是,库函数sort_by需要一个函数。 我们需要的是一个内联匿名函数,它是一个在使用它的同一点声明的函数。

此外,虽然类型规范对于变量声明是可选的,但是参数和函数声明的返回值是必需的。 当这些函数从远程语句调用时,以及可能由其他程序员调用时,这些规范(如函数名称)很方便。 但是当你必须在声明它的地方编写一个要调用的函数时,这种类型规范通常很烦人。 因此,声明和调用内联匿名函数以及其参数的类型推断和返回值将是一个方便的功能。

另一个缺点是需要将功能体封装在支架中。 通常,常用函数包含多个语句,因此将这些语句括在大括号中并不烦人。 相反,匿名函数通常由单个表达式组成,这样可以方便地编写而无需将其括在大括号中。 假设一个块仍然是一个表达式,那么拥有一个带有类型推断的内联匿名函数和一个表达式作为正文将是一件好事。

捕捉环境

到目前为止我们在本章中所说的所有内容在许多其他语言中都是有效的,包括C. 但Rust函数还有一个不寻常的限制:它们无法访问在它们之外声明的任何变量。 您可以访问静态项以及常量,但不能访问堆栈分配(即“let-declared”)变量。 例如,这个程序是非法的:

let two = 2.;
fn print_double(x: f64) {
print!("{}", x * two);
}
print_double(17.2);

它的编译会产生错误:无法捕获fn项中的动态环境。 “动态环境”表示在调用函数时恰好有效的变量集。 在某种程度上,它是“动态的”,因为可以在多个语句中调用给定的函数,并且在其中一个语句中有效的变量在另一个语句中可能无效。 “捕获环境”意味着能够访问这些变量。
相反,这是有效的:

const TWO: f64 = 2.;
fn print_double(x: f64) {
print!("{}", x * TWO);
}
print_double(17.2);
and this too:
static TWO: f64 = 2.;
fn print_double(x: f64) {
print!("{}", x * TWO);
}
print_double(17.2);

这样的限制有一个很好的理由:外部变量有效地进入函数的编程接口,但它们在函数签名中并不明显,因此它们对于理解代码具有误导性。

但是当一个函数只能在已定义的位置被调用时,它访问外部变量的事实并不会使它变得不易理解,因为这些外部变量已经可用于声明语句。

因此,我们的功能要求如下:内联匿名函数,带类型推断; 单个表达式作为正文; 并捕获任何有效变量。

闭包

由于它非常有用,在Rust中有一个名为“闭包”的功能。 闭包只是一种更简单的函数,适合定义小的匿名函数,并在定义它们的地方调用它们。

实际上,您还可以定义一个闭包,将其分配给一个变量,然后给它一个名称,然后使用它的名称调用它。 但这不是闭包的最典型用法。 甚至类型规格也是可能的。 这是上面的降序排序示例,使用闭包而不是desc函数执行:

let mut arr = [4, 8, 1, 10, 0, 45, 12, 7];
use std::cmp::Ordering;
let desc = |a: &i32, b: &i32| -> Ordering {
if a < b { Ordering::Greater }
else if a > b { Ordering::Less }
else { Ordering::Equal }
};
arr.sort_by(desc);
print!("{:?}", arr);

与前一个示例的唯一区别是:

•使用let关键字而不是fn。

•在闭包名称后面添加了=符号。

•包含函数参数的(和)符号已被|替换 (管)符号。

•关闭声明后添加了分号。

到目前为止,没有任何优点,但我们说闭包可以定义在必须使用的位置,并且类型和大括号是可选的。 因此,我们可以将之前的代码转换为以下代码:

let mut arr = [4, 8, 1, 10, 0, 45, 12, 7];
use std::cmp::Ordering;
arr.sort_by(|a, b|
if a < b { Ordering::Greater }
else if a > b { Ordering::Less }
else { Ordering::Equal });
print!("{:?}", arr);

这已经是一个很好的简化。 但还有更多。

标准库已经包含cmp函数(“compare”的简写); 此函数根据其两个参数中的哪一个更大来返回Ordering值。 以下两个陈述是等效的:

arr.sort();
arr.sort_by(|a, b| a.cmp(b));

因此,要获得反转顺序,您可以无差别地使用以下每个语句:

arr.sort_by(|a, b| (&-*a).cmp(&-*b));
arr.sort_by(|a, b| b.cmp(a));

这是一个完整的例子:

plete examp
let mut arr = [4, 8, 1, 10, 0, 45, 12, 7];
arr.sort_by(|a, b| b.cmp(a));
print!("{:?}", arr);

此外,use指令已被删除,因为它不再需要了。

其他例子

这是另一个显示调用闭包的方法的示例:

let factor = 2;
let multiply = |a| a * factor;
print!("{}", multiply(13));
let multiply_ref: &(Fn(i32) -> i32) = &multiply;
print!(
" {} {} {} {} {}",
(*multiply_ref)(13),
multiply_ref(13),
(|a| a * factor)(13),
(|a: i32| a * factor)(13),
|a| -> i32 { a * factor }(13));

这将打印:“26 26 26 26 26 26”。

该程序包含六个相同的闭包调用。 每个都采用名为a的i32参数; 将其乘以捕获的变量因子,其值为2; 并返回这种乘法的结果。 参数总是13,因此结果总是26。

在第二行中,声明第一个闭包,使用参数a和返回值的类型推断。 闭包的主体访问由前一个语句声明的外部变量因子,因此在闭包内捕获此变量及其当前值。 然后使用闭包来初始化变量multiply,其类型是推断的。

在第三行中,调用分配给乘法变量的闭包就像任何函数一样。

在第四行中,刚刚声明的闭包的地址用于初始化multiply_ref变量。 也可以推断出此变量的类型,但已明确指定。 单词Fn用于指定函数的类型。 每个函数都有一个由其参数类型及其返回值确定的类型。 表达式Fn(i32) - > i32表示“将i32作为参数并返回i32的函数的类型”。 这样的类型表达式前面带有&符号,因为我们所拥有的是“对函数的引用”,而不是“函数”。

在第七行中,对函数的引用被解除引用,获得函数,并且调用该函数。

在第八行中,在不取消引用引用的情况下调用该函数,因为这种取消引用操作对于函数调用是隐式的。

在最后三个语句中,声明并调用了三个匿名闭包。 第一个推断出参数的类型和返回值的类型; 第二个指定参数的类型并推断返回值的类型; 第三个推断出参数的类型,并指定返回值的类型。

请注意,参数13传递给始终括在括号中的闭包。 为了避免将这样的表达式(13)与指定闭包的前一个表达式混淆,在某些情况下,这样的闭包表达式也必须括在括号中。 相反,在最后一种情况下,闭包的主体必须用大括号括起来,以便将它与返回值类型规范分开。

当闭包包含多个语句时,也需要括号,如下例所示:

print!(
"{}",
(|v: &Vec<i32>| {
let mut sum = 0;
for i in 0..v.len() {
sum += v[i];
}
sum
})(&vec![11, 22, 34]));

这将打印“67”,它是矢量中包含的数字的总和。

在这种情况下,需要指定参数的类型,否则编译器无法推断它,并且它将发出错误消息“此值的类型必须在此上下文中已知”,关于表达式v.LEN()。

猜你喜欢

转载自blog.csdn.net/m0_37696990/article/details/82955213
今日推荐