0

I'm trying to create a struct that does a specific behaviour, based on another function passed as parameter.

Here is a minimal example :

struct A {
    behaviour: fn(u8) -> ()
}

impl A {
    pub fn new(
        num_gen: fn(u8) -> u8
    ) -> A {
        let behaviour = |num: u8| {
            let num = num_gen(num);
            println!("number: {num}");
        };
        
        A {
            behaviour,
        }
    }
    
    pub fn call(&self, num: u8) {
        (self.behaviour)(num);
    }
}

fn main() {
    let a = A::new(|x: u8| x + 1);
    a.call(1);
}

I would like my behaviour to encapsulate the function passed as a parameter, but this does not compile as the closure can't coerce to a function pointer.

I tought about saving the passed function as well, and expanding the call method, but in my case I am also using the fn() to hide a generic type like so:


struct A {
    behaviour: fn(u8) -> ()
}

impl A {
    pub fn new<T>(
        obj_gen: fn(u8) -> T
    ) -> A
    where T: core::fmt::Display
    {
        let behaviour = |num: u8| {
            let obj = obj_gen(num);
            println!("size: {}, obj: {obj}", std::mem::size_of::<T>());
        };
        
        A {
            behaviour,
        }
    }
    
    pub fn call(&self, num: u8) {
        (self.behaviour)(num);
    }
}

fn main() {
    let a = A::new(|x: u8| x + 1);
    a.call(1);
}

Maybe this is the XY problem, what I'm trying to do is some abstraction over a struct that can create an object, and use this object in some behaviour whatever the object type is. I cannot make the struct a generic, as I want to store multiple of those in a Vec, with different types. Finally, I do not really care what the object is as long as I can generate it, and use it in other generic functions. But here, functions pointer felt a nice way to do this.

Edit:

As I already mentionned, this is different from this answer, as my struct can NOT be generic. This is why I have function pointers, to hide the hidden type. I need it to be not generic to store multiple of those in a Vec.

2 Answers2

0

As mentionned by Jmb, converting the type to a Box<dyn Fn()> works, if we tell the closure to capture environment by value with the move keyword:

struct A {
    behaviour: Box<dyn Fn(u8)>
}

impl A {
    pub fn new<T: 'static>(
        obj_gen: fn(u8) -> T
    ) -> A
    where T: core::fmt::Display
    {
        let behaviour = Box::new(move |num: u8| {
            let obj = obj_gen(num);
            println!("size: {}, obj: {obj}", std::mem::size_of::<T>());
        });
        
        A {
            behaviour,
        }
    }
    
    pub fn call(&self, num: u8) {
        (self.behaviour)(num);
    }
}

fn main() {
    let a = A::new(|x: u8| x + 1);
    a.call(1);
}
0

As has already been mentioned in LucioleMaléfique’s answer, you can solve this problem using boxed closures.

For a precise argument for why we cannot do what you want using function pointers (using the generic version of your code), consider that for any Rust program R, there are only finitely valid values of type fn(u8). However, using your API, you can create arbitrarily many distinct values of type A at runtime as follows:

fn generate_as(n: usize) -> Vec<A> {
    let mut vec = vec![A::new(|_x| ())];
    for i in 0..n {
        vec.push(A::new(vec[i].behaviour);
    }
    vec

}

We can generate arbitrarily many distinct values of type A, and thus arbitrarily many distinct function pointers. This is impossible.

Mark Saving
  • 1,752
  • 7
  • 11