如何在Rust函数签名中使用Fn traits(闭包)?

18
我想编写一个返回的函数,它接受一个没有参数的闭包,一个接受一个参数的闭包和一个接受两个参数的闭包,其中所有闭包参数的类型都是,并且每个闭包都返回。
那么这个函数的签名将是什么样子的?
我想通过FnFnMut traits来接受它们。这个签名是什么样子的?需要使用crate中的特性吗?如果需要,是哪些特性以及为什么?
如果已知:糖语法是什么样子的?解糖之后呢?
如果已知:未来可能会有哪些变化?
2个回答

15

我想编写一个返回的函数,该函数接受一个不带参数的闭包、一个带一个参数的闭包和一个带两个参数的闭包,其中所有闭包参数都是类型,每个闭包都返回f32>。

那么这个函数的签名会是什么样子呢?

它看起来会像这样:

fn closures<F1, F2, F3>(mut f1: F1, mut f2: F2, mut f3: F3) -> i32
where
    F1: FnMut() -> f32,
    F2: FnMut(i32) -> f32,
    F3: FnMut(i32, i32) -> f32,
{
    (f1() + f2(10) + f3(20, 30)) as i32
}

fn main() {
    let x = closures(|| 0.1, |x| (2 * x) as f32, |x, y| (x + y) as f32);
    println!("{}", x);
}

如果您希望强制调用方传递不改变其环境的闭包,可以使用 Fn 代替 FnMut(并在 f1f2f3 前删除 mut),但通常情况下,我认为您会想使用 FnMut

此代码使用非盒装闭包语法和重载调用。如果没有它们,它将如下所示:

#![feature(unboxed_closures, fn_traits)]

fn closures<F1, F2, F3>(mut f1: F1, mut f2: F2, mut f3: F3) -> i32
where
    F1: FnMut<(), Output = f32>,
    F2: FnMut<(i32,), Output = f32>,
    F3: FnMut<(i32, i32), Output = f32>,
{
    (f1.call_mut(()) + f2.call_mut((10,)) + f3.call_mut((20, 30))) as i32
}

fn main() {
    let x = closures(|| 0.1, |x| (2 * x) as f32, |x, y| (x + y) as f32);
    println!("{}", x);
}

糖语法可以美化闭包类型语法,并且重载调用功能允许省略显式的call_*方法。

Rust 1.0之前

编辑注:此问题在Rust 1.0之前提出,本部分仅适用于那时与1.0之间发生的更改。

至于未来将要发生的变化,可能会简化闭包构造语法(当当前闭包被放弃时),因此main()部分将从以下内容更改:

fn main() {
    let x = closures(
        |&mut:| 0.1,
        |&mut: x: int| (2*x) as f32,
        |&mut: x: int, y: int| (x + y) as f32
    );
    println!("{}", x);
}

看起来像这样:

fn main() {
    let x = closures(
        || 0.1,
        |x| (2*x) as f32,
        |x, y| (x + y) as f32
    );
    println!("{}", x);
}

闭包的实际类型(FnMutFnFnOnce)将被推断出来。

还会有其他变化,例如用于从函数返回的闭包的move关键字(move会影响变量捕获语义)。这在已接受的RFC中进行了讨论。

通常,在 RFC 中概述了未装箱的闭包。然而,它没有更新新的闭包语法糖和其他微妙的更改,可能更好的方式是跟随Rust问题追踪器以获取更多信息。例如,许多未装箱的闭包问题都聚集在bug中。


哦,我在查找RFC文档中的Fn traits信息时漏掉了它,因为我一直在寻找“traits”而不是“closures”。谢谢! - user
没错,这些东西被称为未装箱的闭包,在你搜索这个术语时会找到很多相关内容。 - Vladimir Matveev

11

FnFnMutFnOnce 是使用非盒式闭包引入的三种特质类型。这些特质之间除了单个方法名称不同以外,最大的区别是它们的方法中的 self 参数传递方式不同:

  • Fn: &self(通过引用传递,无法更改闭包的环境)
  • FnMut: &mut self(通过引用传递,可以更改闭包的环境)
  • FnOnce: self(通过值传递,消耗闭包,因此无法多次调用闭包)

这些特质都有一个类型参数 Args,它是表示闭包参数的元组类型(如果闭包不带参数,则为 ())。FnOnce 具有关联类型 Result,它是闭包的返回类型。 FnFnMut 的子特质,而 FnMutFnOnce 的子特质,这意味着 FnFnMutFnOnce 中“继承”了 Result。非盒式闭包会自动实现适用的特质。

语法糖形式

不带参数的闭包

fn foo<F: Fn() -> f32>(closure: F) -> i32 {
    0
}

闭包接受一个参数

fn foo<F: Fn(i32) -> f32>(closure: F) -> i32 {
    0
}

闭包接受两个参数

fn foo<F: Fn(i32, i32) -> f32>(closure: F) -> i32 {
    0
}

使用where子句

每个查询语句也可以使用where语法:

fn foo<F>(closure: F) -> i32
where
    F: Fn() -> f32,
{
    0
}

参见:

使用impl trait语法:

fn foo_impl(closure: impl Fn() -> f32) -> i32 {
    0
}

参见:

展开版本

这种格式不稳定,每个示例都需要使用特性门限 #![feature(unboxed_closures)]。您也可以使用 whereimpl trait 语法。

参见:

没有参数的闭包

fn foo<F: Fn<(), Output = f32>>(closure: F) -> i32 {
    0
}

闭包接受一个参数

fn foo<F: Fn<(i32,), Output = f32>>(closure: F) -> i32 {
    0
}

带有两个参数的闭包

fn foo<F: Fn<(i32, i32), Output = f32>>(closure: F) -> i32 {
    0
}

旧的“盒装”闭包

在这个问题被提出的时候,“盒装”闭包是存在的,但是它们在 Rust 1.0 发布之前被移除了

这个 metabug跟踪了非盒装闭包的开发。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接