写点什么

Rust 从 0 到 1- 高级特性 - 函数和闭包进阶

用户头像
关注
发布于: 4 小时前
Rust从0到1-高级特性-函数和闭包进阶

下面我们将讨论函数和闭包相关的一些高级特性,包括函数指针和返回闭包。

函数指针

之前我们已经讨论过如何在函数中使用闭包;我们也可以把普通的函数做为参数传递给函数!这适用于我们希望使用现有的函数而不是定义一个新的闭包时。函数指针允许我们把一个函数作为另一个函数的参数。函数的类型是 fn (注意,是小写的 f ,不要与闭包对应的 Fn trait 混淆),称为函数指针(function pointer)。参考下面的例子:

fn add_one(x: i32) -> i32 {    x + 1}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg)}
fn main() { let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);}
复制代码

上例中的这段代码会打印出 The answer is: 12。do_twice 的参数 f 是 fn 类型,它有一个 i32 类型的参数并返回 i32 类型的结果,我们可以在函数体中通过参数名 f 调用函数。在 main 中,我们函数 add_one 作为参数传递给 do_twice,这意味着在调用参数 f 的时候,我们将实际调用 add_one 函数。

因为 fn 是一个类型而不是一个 trait,所以和闭包不同,我们直接指定 fn 做为参数类型,而不是使用以 Fn 做为 trait bound 的泛型参数。

函数指针实现了闭包的所有三个 trait(Fn、FnMut 和 FnOnce),所以我们也可以把函数指针做为参数传递给以闭包做为参数的函数。因此,最好是在函数参数中使用泛型参数和闭包的 trait 做为 trait bound,这样函数指针或闭包都可以做为参数。但是,有一种场景我们只能使用 fn,即与不存在闭包的语言的代码交互时:C 语言的函数可以接受函数作为参数,但它没有闭包。

下面我们通过 map 来看一个既可以使用闭包又可以使用函数指针的例子,先来看看使用闭包:

let list_of_numbers = vec![1, 2, 3];let list_of_strings: Vec<String> =    list_of_numbers.iter().map(|i| i.to_string()).collect();
复制代码

我们还可以将函数作为 map 的参数来代替闭包:

let list_of_numbers = vec![1, 2, 3];let list_of_strings: Vec<String> =    list_of_numbers.iter().map(ToString::to_string).collect();
复制代码

注意,因为存在多个 to_string 函数,我们使用了完全限定语法,即定义于 ToString trait 的 to_string 函数,Rust 的标准库为所有实现了 Display trait 的类型实现了这个 trait。

另外还有一种利用元组结构体或元组结构体类型枚举成员实现的模式。元组结构体类型使用 () 作为初始化语法,看起来像函数调用,而实际上初始化程序的实现也是一个函数,它根据参数构造实例并返回。我们可以像实现了闭包 trait 的函数一样使用这些初始化函数,参考下面的例子:

enum Status {    Value(u32),    Stop,}
let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
复制代码

上例中我们通过将集合里的每个 u32 值做为参数调用 Status::Value 的初始化函数创建 Status::Value 实例。有些人喜欢这种风格的编码,有些人更喜欢使用闭包的风格,它们最终都会编程成同样的代码,所以我们可以根据自己的喜好选择任何一种。

返回闭包

我们在前面提到过闭包有三个 trait,但没有具体的类型,这意味着无法直接返回闭包。在大部分场景下,如果我们希望返回一个 trait,可以返回实现了该 trait 的具体类型的值做为函数的返回值。但是我们无法返回闭包,因为它们没有具体的类型;和作为参数不同,我们无法使用函数指针 fn 作为返回值类型返回。参考下面的例子:

fn returns_closure() -> dyn Fn(i32) -> i32 {    |x| x + 1}
复制代码

如果尝试编译上例中的代码我们会得到类似如下错误:

$ cargo build   Compiling functions-example v0.1.0 (file:///projects/functions-example)error[E0746]: return type cannot have an unboxed trait object --> src/lib.rs:1:25  |1 | fn returns_closure() -> dyn Fn(i32) -> i32 {  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time  |  = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of type `[closure@src/lib.rs:2:5: 2:14]`, which implements `Fn(i32) -> i32`  |1 | fn returns_closure() -> impl Fn(i32) -> i32 {  |                         ^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0746`.error: could not compile `functions-example`
To learn more, run the command again with --verbose.
复制代码

错误又是和 Sized trait 有关!Rust 不知道需要多少空间来储存闭包。不过我们前面解决过类似问题,我们可以使用 trait 对象来解决:

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

上面的这段代码可以编译了。有关 trait 的更多内容,可以参考之前介绍 trait 的章节。

发布于: 4 小时前阅读数: 3
用户头像

关注

公众号"山 顽石"欢迎大家关注;) 2021.03.23 加入

IT老兵,关注Rust、Java、前端、架构、大数据、AI、算法等;平时喜欢美食、旅游、宠物、健身、篮球、乒乓球等。希望能和大家交流分享。

评论

发布
暂无评论
Rust从0到1-高级特性-函数和闭包进阶