functionrustlambdaclosuresoptional-parameters

Function returning a function in rust and Option


I am currently learning rust and I am having trouble understanding why my code fails and how to fix it.

I want to create a function that returns another function. The second function behaviour should depend on the parameters to the first function. This function created by the first function will be used later in a third function. This third function should have a "default input function" when the users does not explicitly define the input function.

The following code compiles, but does not achieve what I want.

// This code compiles, but is useless
type RGB = [u8; 3];
type Image = Vec<RGB>;

pub fn color_image(image: Image, coloring_function: fn(f64) -> RGB) -> Image {
    // apply `coloring_function` to all processed pixels of image and return it
    return image;
}

pub fn color_interpolation_generator(color_start: RGB, color_end: RGB) -> fn(f64) -> RGB {
    return |x: f64| -> RGB {
        return [0, 0, 0];
    };
}

fn main() {
    let mut image: Image = vec![];

    let color_interpolation_function = color_interpolation_generator([0, 0, 0], [255, 255, 255]);

    image = color_image(image, color_interpolation_function);
}

The function color_interpolation_generator should make use of its parameters to create the returned function. If I modify it in the following way I will get an error:

pub fn color_interpolation_generator(color_start: RGB, color_end: RGB) -> fn(f64) -> RGB {
    return |x: f64| -> RGB {
        return color_start;
    };
}

Error:

expected fn pointer, found closure
   |
   = note: expected fn pointer `fn(f64) -> [u8; 3]`
                 found closure `[closure@src/main.rs:10:12: 12:6]`
note: closures can only be coerced to `fn` types if they do not capture any variables

After some searching I did some modifications and the code compiles again:

// This code does not compile
type RGB = [u8; 3];
type Image = Vec<RGB>;

pub fn color_image(image: Image, coloring_function: impl Fn(f64) -> RGB) -> Image {
    // apply `coloring_function` to all processed pixels of image and return it
    return image;
}

pub fn color_interpolation_generator(color_start: RGB, color_end: RGB) -> impl Fn(f64) -> RGB {
    return move |x: f64| -> RGB {
        let color = color_start;
        return [0, 0, 0];
    };
}

fn main() {
    let mut image: Image = vec![];

    let color_interpolation_function = color_interpolation_generator([0, 0, 0], [255, 255, 255]);

    image = color_image(image, color_interpolation_function);
}

However I also want the color_function to have a default behaviour if no function is passed. I think the correct way to achieve this is using Option. I modify the code accordingly:

type Image = Vec<[u8; 3]>;

pub fn color_image(image: Image, coloring_function: Option<impl Fn(f64) -> [u8; 3]>) -> Image {
    // apply `coloring_function` to all processed pixels of image and return it
    let function = coloring_function.unwrap_or(color_interpolation_generator(
        [255, 255, 255],
        [255, 255, 255],
    ));
    return image;
}

pub fn color_interpolation_generator(
    color_start: [u8; 3],
    color_end: [u8; 3],
) -> impl Fn(f64) -> [u8; 3] {
    return move |x: f64| -> [u8; 3] {
        let color = color_start;
        return [0, 0, 0];
    };
}

fn main() {
    let mut image: Image = vec![];

    let color_interpolation_function = color_interpolation_generator([0, 0, 0], [255, 255, 255]);

    image = color_image(image, Some(color_interpolation_function));
}

but I get the following error:

expected type parameter `impl Fn(f64) -> [u8; 3]`, found opaque type
...
15 |   ) -> impl Fn(f64) -> [u8; 3] {
   |        ----------------------- the found opaque type
   |
   = note: expected type parameter `impl Fn(f64) -> [u8; 3]`
                 found opaque type `impl Fn(f64)-> [u8; 3]`

I have no idea how to fix this and at this point I am not sure if what I want can be done or if my approach is completly wrong. What is the recommended way to achieve my wanted behaviour?


Solution

  • The problem is that coloring_function has some (generic) type, impl Fn(f64) -> [u8; 3], and color_interpolation_generator() returns some type, impl Fn(f64) -> [u8; 3], and they are distinct types. Therefore, you cannot unwrap_or an Option of the first and put there an instance of the second.

    The usual thing to do when you need to choose one of different types is to use Box<dyn Trait>, in your case Box<dyn Fn(f64) -> [u8; 3]>. Change color_image() like:

    pub fn color_image(image: Image, coloring_function: Option<Box<dyn Fn(f64) -> [u8; 3]>>) -> Image {
        // apply `coloring_function` to all processed pixels of image and return it
        let function = coloring_function.unwrap_or_else(|| {
            Box::new(color_interpolation_generator(
                [255, 255, 255],
                [255, 255, 255],
            ))
        });
        return image;
    }
    

    (I changed the unwrap_or() to unwrap_or_else() to avoid allocating the Box when the argument is present).

    And call it like:

    image = color_image(image, Some(Box::new(color_interpolation_function)))