文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
5.2.1 参数
每个闭包类型都是唯一且匿名的,故无法提供参数类型声明。为此,以特征进行分类和约束,所有闭包依照捕获方式自动实现下述某些特征(trait,类似接口)。
// 可多次调用。 trait Fn() -> R { fn call(&self) -> R; } // 可多次调用,可能会修改状态。 trait FnMut() -> R { fn call_mut(&mut self) -> R; } // 仅调用一次,可能会转移所有权。 trait FnOnce() -> R { fn call_once(self) -> R; }
fn demo<F>(f: F) where F: Fn(i32) -> i32 { }
- 如果 F 实现
Fn
特征,那么&F
也会实现。 - 如果 F 实现
FnMut
特征,那么&mut F
也会实现。
编译器会对闭包实参进行检查,判断是否符合形参要求。如果,函数内部多次调用(
Fn
),而闭包实参实现方式为FnOnce
,那么编译将无法完成。相反的,如果参数要求FnOnce
,那么所有闭包,乃至普通函数都符合该需要。
这里涉及特征的子集和超集问题,
Fn: FnMut: FnOnce
。实现
Fn
子集,必然实现FnMut
超集;实现FnMut
,同样要实现FnOnce
超集。反之不然!
形参 符合要求的实参 +========+===+ | FnOnce | 1 | <---- FnOnce, FnMut, Fn, fn +--------+---+ | FnMut | n | <---- FnMut, Fn, fn +--------+---+ | Fn | n | <---- Fn, fn +========+===+
声明
参数是闭包还是函数指针,完全不同。毕竟,闭包结构和指针类型不同,且传参方式也不一样。
fn call(f: fn()) { // function pointer f(); } fn main() { let a = 1; let f = || println!("abc"); // anonymous function let c = || println!("{}", &a); // closure call(f); call(c); ^ expected fn pointer, found closure }
要接收闭包参数,正确做法是用特征和泛型约束。
fn call<F>(f: F) where F: Fn(i32) -> i32 { f(1); f(1); } fn main() { let a = 1; let c = |x| a + x; // Fn call(c); }
fn call<F>(f: F) where F: Fn(i32) -> i32 { f(1); f(1); } fn main() { let mut a = 1; let c = |x| { a += 1; a + x }; ^^^ - closure is `FnMut` because it mutates the variable `a` here | this closure implements `FnMut`, not `Fn` call(c); ---- the requirement to implement `Fn` derives from here }
fn call<F>(mut f: F) // 必须声明为 mut !!! where F: FnMut(i32) -> i32 { f(1); f(1); } fn main() { let mut a = 1; let c = |x| a + x; // Fn call(c); let c = |x| { a += 1; a + x }; // FnMut call(c); }
fn call<F>(mut f: F) where F: FnOnce(i32) -> i32 { f(1); ---- `f` moved due to this call f(1); ^ value used here after move } fn main() { let mut a = 1; let c = |x| a + x; // Fn call(c); let c = |x| { a += 1; a + x }; // FnMut call(c); }
函数指针,可传入任何一种类型。
fn call<F>(f: F) where F: FnOnce() // Fn, FnMut, FnOnce { f(); } fn test() { println!("hello"); } fn main() { call(test); }
复制
编译器根据捕获方式和环境变量,决定闭包是否可 Copy
或 Clone
。
fn main() { let y = 10; let add = |x| x + y; let add2 = add; // copy assert_eq!(add(1), 11); assert_eq!(add2(2), 12); }
fn main() { let mut y = 10; let mut add = |x| { y += x; y }; let mut add2 = add; --- value moved here assert_eq!(add2(1), 11); assert_eq!(add(1), 12); ^^^ value borrowed here after move }
fn main() { let mut y = 10; let add = move |x| { y += x; y }; assert_eq!(add.clone()(1), 11); assert_eq!(add.clone()(2), 12); }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论