Home > Software engineering >  Returning a closure from a method of a generic struct
Returning a closure from a method of a generic struct

Time:04-29

I'm a newbie with Rust and I bumped into some obstacles when dealing with closures, either when returning them from functions and or methods, either when I need to store them as struct fields.

Let's start with what is working:

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 };
}

This works as expected and the type of the closure that is built outside the struct is properly matched with the generic of Foo.

I wanted to achieve the same, but by hiding the creation of the closure simply by moving it inside the impl of Foo itself.

I tested these alternatives, but none of theme compiles:

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
    }
}

I understand that each closure has its own opaque and distinct type, but then I don't understand why on the other hand the initial implementation of Foo works then.

By reading the accepted answer here I seem to understand that the only option, in this case, would be to box the closure, but I still don't have a full understanding.

By combining boxed closure and trait aliases (I know it's not the "real" trait aliasing) I came up with this:

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));
}

But I'm wondering whether it's possible to obtain an unboxed version.

Any clarification would be very appreciated!

CodePudding user response:

The problem in this code:

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

is that to call it, the user code would need to write something like this:

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

And specify some type there. Even if the type is not typed explicitly, it should be resolved there (type omission is mostly syntactic sugar). But the type of the stored value is decided inside that function, in the call to build_func(1), so the user has no type to use there and that function cannot be called.

My advice is to just write a free function:

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

Now the generic type is an impl in the return type of the function, that is an special opaque generic decided by the code of the function. So this works.


If you really, really want to write Foo2::new you can write the function implementation inside a dummy non-generic impl Foo2 block. Usually that would be impl Foo2<()> but the type () does not satisfies your constraints, but you can use any other dummy type that does:

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

(Note that this new() does not return Self because the generic type is not correct.)

And now you can at least write:

let foo2 = Foo2::new();

CodePudding user response:

The difference here is that you're returning Self from new(). Inside impl<F> Foo<F>, Self refers to Foo<F>. And F is a generic parameter - you cannot build a closure of type F, because it's a type your caller decides what it is, not you.

Your first version works because it tells the compiler "I will return some type implementing Fn; I'll leave it to you infer what exactly". On the other hand, the second versions are all "given any type F implementing Fn, I'll give you an instance of that type". That is of course impossible.

Instead you want the compiler to infer the used type here, too. The best solution will be to use impl Trait here, too. But impl Trait in positions other than return type is unstable. It will look like (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
    }
}

Another (quite hacky) solution is to have a generic parameter, but not use it and instead use impl Trait in new(). To not require the caller to specify the redundant parameter (since it can't be inferred anymore as it's unused), we can use a marker type, usually (). This requires us to remove the F: Fn(usize) -> usize bound from the struct and put it only on the impl, however this is a good style anyway (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
    }
}

The last solution is indeed to box the closure, but you don't need a new trait for that - you can use Fn directly (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
    }
}
  • Related