从通用结构的方法返回闭包

Returning a closure from a method of a generic struct

我是 Rust 的新手,在处理闭包时遇到了一些障碍,无论是从函数和/或方法返回它们时,还是当我需要将它们存储为结构字段时。

让我们从有效的开始:

fn build_func(b: usize) -> impl Fn(usize) -> usize {
    move |a| a + b
}

struct Foo<F>
where
    F: Fn(usize) -> usize,
{
    pub foo: F,
}

impl<F> Foo<F>
where
    F: Fn(usize) -> usize,
{
    fn new(foo: F) -> Self {
        Self { foo }
    }
}

fn main() {
    let foo1 = Foo { foo: |a| a + 1 };

    let foo2 = Foo { foo: build_func(2) };

    let foo_func = build_func(3);
    let foo3 = Foo { foo: foo_func };
}

这按预期工作,并且在结构外部构建的闭包类型与 Foo 的泛型正确匹配。

我想达到同样的效果,但只需将闭包的创建隐藏起来,只需将其移动到 Foo 本身的 impl 中即可。

我测试了这些替代方案,但 none 的主题编译:

struct Foo2<F>
where
    F: Fn(usize) -> usize,
{
    pub foo: F,
}

impl<F> Foo2<F>
where
    F: Fn(usize) -> usize,
{
    fn new() -> Self {
        let foo = build_func(1);
        Self { foo }
    }
}

struct Foo3<F>
where
    F: Fn(usize) -> usize,
{
    pub foo: F,
}

impl<F> Foo3<F>
where
    F: Fn(usize) -> usize,
{
    fn new() -> Self {
        let foo = |a| a + 1;
        Self { foo }
    }
}

struct Foo4<F>
where
    F: Fn(usize) -> usize,
{
    pub foo: F,
}

impl<F> Foo4<F>
where
    F: Fn(usize) -> usize,
{
    fn new() -> Self {
        let foo = Self::build_func(1);
        Self { foo }
    }

    fn build_func(b: usize) -> F {
        move |a| a + b
    }
}

struct Foo5<F>
where
    F: Fn(usize) -> usize,
{
    pub foo: F,
}

impl<F> Foo5<F>
where
    F: Fn(usize) -> usize,
{
    fn new() -> Self {
        let foo = Self::build_func(1);
        Self { foo }
    }

    fn build_func(b: usize) -> impl Fn(usize) -> usize {
        move |a| a + b
    }
}

我知道每个闭包都有自己的不透明和不同的类型,但我不明白为什么 Foo 的初始实现会起作用。

通过阅读已接受的答案here,我似乎明白在这种情况下,唯一的选择是将闭包框起来,但我仍然没有完全理解。

通过结合盒装闭包和特征别名(我知道这不是“真正的”特征别名)我想到了这个:

trait Func: Fn(usize) -> usize {}
impl<T> Func for T where T: Fn(usize) -> usize {}

struct Foo6 {
    pub foo: Box<dyn Func>,
}

impl Foo6 {
    fn new() -> Self {
        let foo = Self::build_func(1);
        Self { foo: Box::new(foo) }
    }

    fn build_func(b: usize) -> impl Func {
        move |a| a + b
    }
}

fn main() {
    let foo = Foo6::new();
    println!("{}", (foo.foo)(1));
}

但我想知道是否有可能获得未装箱的版本。

任何澄清将不胜感激!

这段代码中的问题:

impl<F> Foo2<F>
where
    F: Fn(usize) -> usize,
{
    fn new() -> Self {
        let foo = build_func(1);
        Self { foo }
    }
}

就是要调用它,用户代码需要这样写:

let foo2 = <Foo2<?>>::new();

并在那里指定一些类型。即使类型没有显式输入,也应该在那里解决(类型遗漏主要是语法糖)。但是存储值的类型是在该函数内部决定的,在对 build_func(1) 的调用中,因此用户无法在那里使用类型,因此无法调用该函数。

我的建议是只写一个免费函数:

fn new_foo2() -> Foo2<impl Fn(usize) -> usize> {
    let foo = build_func(1);
    Foo2 { foo }
}

现在泛型是函数return类型中的一个impl,是由函数代码决定的特殊的不透明泛型。所以这有效。


如果您真的非常想编写 Foo2::new,您可以在虚拟 non-generic impl Foo2 块中编写函数实现。通常这将是 impl Foo2<()> 但类型 () 不满足您的约束,但您可以使用任何其他虚拟类型:

impl Foo2<fn(usize)->usize> {
    fn new() -> Foo2<impl Fn(usize) -> usize> {
        let foo = build_func(1);
        Foo2 { foo }
    }
}

(注意这个new()不是return Self因为泛型类型不正确。)

现在你至少可以写:

let foo2 = Foo2::new();

这里的区别在于您是从 new() returning Self。在impl<F> Foo<F>里面,Self指的是Foo<F>F 是一个通用参数 - 你不能构建一个 F 类型的闭包,因为它是你的调用者决定它是什么的类型,而不是你。

您的第一个版本有效,因为它告诉编译器“我将 return 某些 类型实现 Fn;我会留给您推断什么确切地”。另一方面,第二个版本都是“给定 any 类型 F 实现 Fn,我会给你一个那个类型的实例”。那当然是不可能的。

相反,您希望编译器也在这里推断使用的类型。最好的解决方案是在这里也使用 impl Trait 。但是 impl Trait 在 return 类型以外的位置是不稳定的。它看起来像 (playground):

#![feature(type_alias_impl_trait)]

type InnerFn = impl Fn(usize) -> usize;

struct Foo {
    pub foo: InnerFn,
}

impl Foo {
    fn new() -> Self {
        let foo = Self::build_func(1);
        Self { foo }
    }
    
    fn build_func(b: usize) -> InnerFn {
        move |a| a + b
    }
}

另一个(相当老套的)解决方案是有一个通用参数,但不使用它而是在 new() 中使用 impl Trait。为了不要求调用者指定冗余参数(因为它不再被使用而无法推断),我们可以使用标记类型,通常是 ()。这要求我们从结构中删除 F: Fn(usize) -> usize 绑定并将其仅放在 impl 上,但是 (playground):

struct Foo<F> {
    pub foo: F,
}

impl Foo<()> {
    fn new() -> Foo<impl Fn(usize) -> usize> {
        let foo = Self::build_func(1);
        Foo { foo }
    }
    
    fn build_func(b: usize) -> impl Fn(usize) -> usize {
        move |a| a + b
    }
}

最后一个解决方案确实是将闭包装箱,但你不需要新的特征 - 你可以直接使用 Fn (playground):

struct Foo {
    pub foo: Box<dyn Fn(usize) -> usize>,
}

impl Foo {
    fn new() -> Self {
        let foo = Self::build_func(1);
        Self { foo: Box::new(foo) }
    }

    fn build_func(b: usize) -> impl Fn(usize) -> usize {
        move |a| a + b
    }
}