A vec of fn pointers — type mismatch

I am trying to create a vector of functions to apply them later. I created a type alias for those functions, the functions themselves, and then I tried adding them to a vector. The funny thing is that I can add one of them, but not both. Here's the code snippet:

type MathAction<'a> = fn(&'a Action<'a>, &'a Action<'a>) -> Option<Action<'a>>;

fn add<'a>(x: &'a Action<'a>, y: &'a Action<'a>) -> Option<Action<'a>> {
    Some(Action::Add(x, y))
}


fn sub<'a>(x: &'a Action<'a>, y: &'a Action<'a>) -> Option<Action<'a>> {
    let (x, y) = sorted_nums(x, y);
    if x.value() != y.value() {
        Some(Action::Sub(x, y))
    } else {
        None
    }
}

fn sorted_nums<'a> (a: &'a Action<'a>, b: &'a Action<'a>) -> (&'a Action<'a>, &'a Action<'a>) {
    if a.value() > b.value() {
        (a, b)
    } else {
        (b, a)
    }
}

enum Action<'a> {
    Add(&'a Action<'a>, &'a Action<'a>),
    Sub(&'a Action<'a>, &'a Action<'a>),
    // Mul(Box<Action>, Box<Action>),
    // Div(Box<Action>, Box<Action>),
    Simple(i32),
}


impl <'a> Action<'a> {
    fn new(x: i32) -> Action<'a> {
        Action::Simple(x)
    }
    fn value(&self) -> i32 {
        match self {
            Action::Simple(x) => *x,
            Action::Add(x, y) => x.value() + y.value(),
            Action::Sub(x, y) => x.value() - y.value(),
            // Action::Mul(x, y) => x.value() * y.value(),
            // Action::Div(x, y) => x.value() / y.value(),
        }
    }
}

fn main() {
    let math_actions: &Vec<MathAction> = &vec![add, sub];
}

The error I get is on sub in &vec![add, sub]:

expected fn pointer `fn(&Action<'_>, &Action<'_>) -> Option<Action<'_>>`
found fn pointer `for<'a> fn(&'a Action<'a>, &'a Action<'a>) -> Option<Action<'a>>`rustc[E0308](https://doc.rust-lang.org/error-index.html#E0308)

The weird thing is that both add and sub have the same signature, but there's no error on add, and if I remove add, there will be no error on sub either.
What am I missing?

This type alias:

type MathAction<'a> = fn(&'a Action<'a>, &'a Action<'a>) -> Option<Action<'a>>;

is a type alias which, given a singular lifetime 'a, aliases a function pointer that works with that one singular lifetime. There's a different function pointer type per lifetime.

In contrast this type alias:

type MathAction = for<'a> fn(&'a Action<'a>, &'a Action<'a>) -> Option<Action<'a>>;

is an alias of one particular type, a function pointer that can handle any lifetime at all. It's a higher-ranked function pointer.

Your functions:

fn sub<'a>(x: &'a Action<'a>, y: &'a Action<'a>) -> Option<Action<'a>>

are higher-ranked too, so changing the type alias is an option.


As for what's going on in your OP, apparently when the vec![] hold only one function item, the compiler correctly infers a coercion to the subtype for some arbitrary lifetime, but then fails to infer the coercion for two or more items. I agree it seems it should be able to do this on its own. You can make it work by explicitly nudging the compiler to make a coercion:

    let math_actions: &Vec<MathAction<'_>> = &vec![add, sub as _];

And now for some unsolicited advice independent of your question:

  • There's no need for references in your main
    let math_actions: Vec<MathAction> = vec![add, sub];
    
  • I think you'll have an easier time with the non-lifetime-carrying Box<Action> version that's commented out for Mul and Div
  • Since you're maintaining semantic information about the operation in the enum, you may not need to carry around function pointers at all (if you know it's an Action::Sub, you know to use fn sub)
3 Likes

Thanks, I changed the type alias to the one with "for", and it works. As for the variant with Boxes — I'm trying to figure out how references and lifetimes work, it's a self-educating project.

1 Like