返回介绍

5.2.1 参数

发布于 2024-10-13 11:25:29 字数 3326 浏览 0 评论 0 收藏 0

每个闭包类型都是唯一且匿名的,故无法提供参数类型声明。为此,以特征进行分类和约束,所有闭包依照捕获方式自动实现下述某些特征(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);
}

复制

编译器根据捕获方式和环境变量,决定闭包是否可 CopyClone

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文