Search code examples
rusthigher-order-functions

Capturing extra variables on a match statement assigning a function


I am trying to assign first to a function that returns an attribute of player depending on which condition is being used. In the Int case, I want to just ignore the player and just return the value that is associated with the Int case of ConditionValue. I looked for other solutions to this problem, but was unable to find something that was similiar enough to apply in this case.

//let first: Fn(&Player) -> u16 = match condition.first {
//let first: dyn Fn(&Player) -> u16 = match condition.first {
let first: fn(&Player) -> u16 = match condition.first {
    // ConditionValue::Int(val) => |_|  val,
    ConditionValue::Int(val) => |_| -> u16 {val},
    ConditionValue::CountInDeck(_) => todo!(),
    ConditionValue::CountTypeInDeck(_) => todo!(),
    ConditionValue::CountInSupply(_) => todo!(),
    ConditionValue::CountAllCardsInDeck => todo!(),
    ConditionValue::CountVp => todo!(),
    ConditionValue::CountOpponentVp => todo!(),
};
error[E0308]: mismatched types
   --> src\strategy.rs:149:37
    |
149 |         ConditionValue::Int(val) => |_| -> u16 {val},
    |                                     ^^^^^^^^^^^^^^^^ expected fn pointer, found closure
    |
    = note: expected fn pointer `for<'a, 'b> fn(&'a Player<'b>) -> u16`
                  found closure `[closure@src\strategy.rs:149:37: 149:47]`
note: closures can only be coerced to `fn` types if they do not capture any variables
   --> src\strategy.rs:149:49
    |
149 |         ConditionValue::Int(val) => |_| -> u16 {val},
    |                                                 ^^^ `val` captured here

I experimented with the commented out portions of code, couldn't get anything that was promising.


Solution

  • As the error indicates, fn (with a lowercase f) types are only usable for actual function pointers. That means pointers to top-level functions or to closures that don't close around any variables.

    You want one of the three function traits. Those are, in order

    • FnOnce is the trait for functions that can be called once and which takes ownership of their closure.
    • FnMut is the trait for functions that can be called once at a time. These take &mut exclusive references to their closure.
    • Fn is the trait for functions that can be called any number of times. These take & immutable references to their closure.

    Obviously, Fn is the strictest of these, with FnOnce being the most general (in the sense that more functions implement FnOnce than implement Fn), so use items later in the above list if you can, since they provide stronger guarantees to the caller.

    But we actually have another problem. I'm going to assume you're using Fn from here on in, but everything I say here applies to the other two as well. Fn is a trait, so if we want to use it as a trait object, we need to write dyn Fn(&Player) -> u16. But dyn trait object types are unsized, so they can't be stored in local variables or directly returned from functions. We need to wrap it in something that can handle an unsized type, and the usual way to do that is with a Box, which stores a pointer to arbitrary (potentially unsized) data.

    So the type you're looking for is Box<dyn Fn(&Player) -> u16> (again, replace Fn with FnMut or FnOnce as needed). And you'll need to wrap the value in a box explicitly.

    let first: Box<dyn Fn(&Player) -> u16> = match condition.first {
        ConditionValue::Int(val) => Box::new(|_| -> u16 {val}),
        ...
    };