【Rust自学】19.3. 高级函数和闭包

【Rust自学】19.3. 高级函数和闭包
19.3 高级函数和闭包19.3.1 函数指针我们已经讲过把闭包传进函数。实际上我们也可以把函数传进函数。在传递时函数会被强制转换成fn类型这就是函数指针。例如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}); }do_twice的第一个参数f的类型是fn也就是函数指针。它期望一个参数类型为i32、返回类型也为i32的函数。函数体中调用了两次f。输出The answer is: 12函数指针与闭包的区别闭包至少实现了Fn、FnOnce和FnMut这三个 trait 之一。函数指针fn是一个类型不是 trait。我们可以直接把fn指定为参数类型而不必声明一个以Fntrait 为约束的泛型参数。函数指针实现了全部三种闭包 trait也就是Fn、FnOnce和FnMut。所以你总是可以把函数指针作为参数传给接受闭包的函数。正因为如此我们编写函数时通常更倾向于使用带闭包 trait 的泛型参数因为这样函数既能接受闭包也能接受普通函数。在某些情况下我们可能想接受fn类型而不是闭包例如与不支持闭包的代码交互时比如 C 函数。该怎么写呢看一个例子fn main() { let list_of_numbers vec![1, 2, 3]; let list_of_strings: VecString list_of_numbers .iter() .map(|i| i.to_string()) .collect(); // 分行只是为了可读性并不是必须的 }list_of_numbers中的元素是i32我们想把它们转换成String赋给list_of_strings。步骤是- 先用iter产生一个迭代器- 再用map中的闭包|i| i.to_string()转换每个元素- 最后用collect把所有转换后的元素收集成一个集合这段代码也可以这样写fn main() { let list_of_numbers vec![1, 2, 3]; let list_of_strings: VecString list_of_numbers .iter() .map(ToString::to_string) .collect(); }区别在于.map(ToString::to_string)这里直接传入了to_string函数。效果与上一版相同。顺便一提ToString::to_string使用了 19.2. 高级 trait关联类型、默认泛型参数和运算符重载、完全限定语法、supertrait 和 newtype 讨论过的完全限定语法。来看一下map的定义fn mapB, F(self, f: F) - MapSelf, F where Self: Sized, F: FnMut(Self::Item) - Bmap要求f实现FnMuttrait而闭包和函数指针都满足这个要求所以两者都可以传入。再看另一个例子fn main() { enum Status { Value(u32), Stop, } let list_of_statuses: VecStatus (0u32..20) .map(Status::Value) .collect(); }注意map的参数。我们使用构造函数Status::Value对范围内的每个u32调用map并创建Status::Value实例。有人可能会问Status::Value不是枚举变体吗怎么变成函数了这是因为在 Rust 中这样的构造函数被实现为接收一个参数并返回新实例的函数。换句话说let v Status::Value(3);这只是一个例子。这里初始化了v而Status::Value(3)可以看作一次构造函数调用3是构造函数的参数。由于构造函数被实现为函数我们可以把它们当作函数来用3就是它们的参数。所以我们也可以把这类构造函数用作实现了闭包 trait 的函数指针。19.3.2 返回闭包闭包通过 trait 来表达因此不能直接从函数返回闭包。相反你可以返回一个实现了该 trait 的具体类型。例如fn returns_closure() - dyn Fn(i32) - i32 { |x| x 1 }这个函数试图直接返回一个闭包。输出error[E0746]: return type cannot be a trait object without pointer indirection -- src/lib.rs:1:25 | 1 | fn returns_closure() - dyn Fn(i32) - i32 { | ^^^^^^^^^^^^^^^^^^ doesnt have a size known at compile-time | help: consider returning an impl Trait instead of a dyn Trait | 1 - fn returns_closure() - dyn Fn(i32) - i32 { 1 fn returns_closure() - impl Fn(i32) - i32 { | help: alternatively, box the return type, and wrap all of the returned values in Box::new | 1 ~ fn returns_closure() - Boxdyn Fn(i32) - i32 { 2 ~ Box::new(|x| x 1) | For more information about this error, try rustc --explain E0746. error: could not compile functions-example (lib) due to 1 previous errorRust 不知道需要多少空间来存储这个闭包所以会报错。还记得我们以前在哪里遇到过同样的“Rust 不知道该分配多少空间”的错误吗没错——在学习链表时。当时的解决办法是用BoxT包裹链表这里也可以这样做fn returns_closure() - Boxdyn Fn(i32) - i32 { Box::new(|x| x 1) }因为返回值位于指针之后返回类型现在在编译时就有了已知大小。