Error: Expected Type Parameter, Found Closure

时间:2019-01-09 21:49:47

标签: generics rust traits

I have the following code:

struct Helper<F1: Fn()> {
    f: F1,
}

struct User<F2: Fn()> {
    h: Helper<F2>,
}

fn new_user<F3: Fn()>() -> User<F3> {
    User {
        // error: expected type parameter, found closure
        h: Helper { f: || {} },
    }
}

fn main(){}

So User needs a Helper<F1> with the type of F1 being specified by User, in this case by the closure in new_user.

This code fails compile with the error expected type parameter, found closure in new_user. As far as I have understood (see for instance this link), this is because the bound of type parameter F3 on new_user is specified by the caller (or signature maybe?), and so although the closure implements the Fn() trait, it won't be able to restrict the type parameter F3 to match the type of the closure. Instead, it is expected that new_user should work with any given F3, which it clearly won't.

So my question is: how do I fix this? Is there any way of expressing that I want new_user to return a User with F2 set to the type of the closure?

I tried to use a type inference placeholder:

// error: the type placeholder `_` is not allowed within types on item signatures
fn new_user() -> User<_> {
    User {
        h: Helper { f: || {} },
    }
}

I could use Box, but this requires modifying Helper, which isn't ideal in my practical case:

struct Helper {
    f: Box<dyn Fn()>,
}

struct User {
    h: Helper,
}

fn new_user() -> User {
    User {
        h: Helper { f: Box::new(|| {}) },
    }
}

fn main(){}

I'm also experimenting a bit with replacing Fn() with a custom trait, which I can implement for User specifically, but so far it's been clunky.

Any suggestions?

1 个答案:

答案 0 :(得分:1)

tl;dr; You probably want this:

fn new_user() -> User<impl Fn()> {
    User {
        h: Helper { f: || {} },
    }
}

More details: Let's look into this:

fn new_user<F3: Fn()>() -> User<F3> {
    User {
        // error: expected type parameter, found closure
        h: Helper { f: || {} },
    }
}

This states that fn_user "accepts" anything satisfying Fn(), but the caller must specify it when calling fn_user. However, to successfully type-check, the supplied type would have to be the same as the one used within the body of new_user, (which is the type of || {}). However, closures have anonymous types, so the caller of new_user cannot know this type, since it cannot look into the implementation of new_user too find out what to supply for F3.

As said, what you want is that new_user returns a User<SomeConcreteType> where SomeConcreteType satisfies F3. The caller does not need to - in fact it should not even - specify SomeConcreteType, because it is inferred within new_user. You can do this as follows:

fn new_user() -> User<impl Fn()> {
    User {
        h: Helper { f: || {} },
    }
}

This way, you specify that new_user can be called without any arguments and it returns a User with some concrete type satisfying Fn().