lobis
lobis

Reputation: 325

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?

Upvotes: 1

Views: 1261

Answers (2)

Scientifik
Scientifik

Reputation: 16

In Rust I would just build a struct with all the data you want to work on including some bool values that indicate which functions to call.

I would then implement a constructor that takes the input parameters necessary to build your struct. Inside this constructor you can use the parameters you pass to the constructor to determine the values of the booleans which get passed to the struct instance which gets returned from the constructor.

This gives you both abstraction and allows you to protect the members of your data and functionality only allowing it to be used as you intend. Here is a general example because I don't exactly understand the context of how your code will be used. The constructor uses the values of the colors passed to it to determine the value of a bool which will be used inside a public function that will call one of two private functions based on whether the bool is true or false. All these members except the constructor method and the return_color_pallete member are private and protected from being used incorrectly.

struct ColorPixels {
     red: i32,
     green: i32,
     blue: i32,
     basic_colors: bool,
}

impl ColorPixels {
     pub fn new(red:i32, green:i32, blue:i32) -> ColorPixels {
          let basic_colors = if r+g+b < 280 {true} else {false};
          ColorPixels {red, green, blue, basic_colors}
     }

     pub fn return_color_pallet(&self) -> Image {
          if &self.basic_colors {return &self.basic();} else {return &self.complex();};
     }

     fn basic(&self) -> Image {
          let basics = vec![];
          basics.push(&self.red);
          basics.push(&self.green);
          basics.push(&self.blue);
          basics
     }


     fn comlex(&self) -> Image {
          ... write some code creating variables out of your inputs modify as you see fit etc...
          let complex = vec![];
          complex.push(complex_red);
          complex.push(complex_green);
          complex.push(complex_blue);
          complex
     }

Upvotes: 0

Chayim Friedman
Chayim Friedman

Reputation: 70910

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)))

Upvotes: 2

Related Questions