1

I would like to make a struct holding a closure.

Also there should be a way to make kind 'default' value of the struct type which is holding no or void computation.

Here is my attempt;

struct Computation<X, Y, F>
where
    F: Fn(&X) -> Y,
{
    f: F,
    _p: PhantomData<(X, Y)>,
}

impl<X, Y, F> Computation<X, Y, F>
where
    F: Fn(&X) -> Y,
{
    fn new(f: F) -> Self {
        Computation {
            f: f,
            _p: PhantomData,
        }
    }

    fn default() -> Self {
        Computation::new(|&_|()) // <- ERROR
    }
}

And here is the compiler output.

mismatched types
expected type parameter `F`
          found closure `[closure@src/lib.rs:212:26: 212:30]`
every closure has a distinct type and so could not always match the caller-chosen type of parameter `F`rustcClick for full compiler diagnostic
lib.rs(200, 12): this type parameter
lib.rs(212, 9): arguments to this function are incorrect
lib.rs(204, 8): associated function defined here

It seems that trying to specifying the concrete type of F 'after the call` if default is the problem. Then, is there any way to implement 'default constructor' for these kind of structs?

lighthouse
  • 413
  • 2
  • 10

2 Answers2

1

You can't know the type of a closure in advance. However, if your closure doesn't, well, close around any variables, then it can be converted to a function pointer type fn(&X) -> Y (Note the lowercase "f"). Consider

impl<X> Computation<X, (), fn(&X) -> ()> {
  fn default() -> Self {
    Computation::new(|_| ())
  }
}

Note that this goes in a separate impl block from your new function, which is parameterized by all three of X, Y, and F. This impl, on the other hand, is only available when Y is () and F is fn(&X) -> () (the type of function pointers from &X to ()).

Also, since you're calling this function default, you may as well go all the way and let it be a true Default value.

impl<X> Default for Computation<X, (), fn(&X) -> ()> {
  fn default() -> Self {
    Computation::new(|_| ())
  }
}

Note that we can only know the type in advance because we don't close around any variables. fn(&X) -> Y is the type of function pointers, which includes both top-level functions and closures that don't actually close (which are compiled to top-level functions internally). fn(&X) -> Y implements Fn(&X) -> Y, but so do a lot of other nontrivial closures, so not every closure is convertible to fn(&X) -> Y. |_| () simply is because it doesn't use any variables from the enclosing scope.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
0

No, you can’t produce a value of an arbitrary type F. Things you could do to progress include making it optional:

#[derive(Default)]
pub struct Computation<X, Y, F>
where
    F: Fn(&X) -> Y,
{
    f: Option<F>,
    _p: PhantomData<fn(X) -> Y>,
}

impl<X, Y, F> Computation<X, Y, F>
where
    F: Fn(&X) -> Y,
{
    pub fn new(f: F) -> Self {
        Self {
            f: Some(f),
            _p: PhantomData,
        }
    }

    pub fn apply(&self, x: &X) -> Y {
        match &self.f {
            Some(f) => f(x),
            None => todo!(),
        }
    }
}

or using dynamic dispatch:

pub struct Computation<'a, X, Y> {
    f: Box<dyn Fn(&X) -> Y + 'a>,
}

impl<'a, X, Y> Computation<'a, X, Y> {
    pub fn new(f: impl Fn(&X) -> Y + 'a) -> Self {
        Self {
            f: Box::new(f),
        }
    }

    pub fn apply(&self, x: &X) -> Y {
        (self.f)(x)
    }
}

impl<'a, X, Y> Default for Computation<'a, X, Y> {
    fn default() -> Self {
        Self::new(|_| todo!())
    }
}

As you can see from the todo!(), the next problem you run into is that you can’t get a value of an arbitrary type Y. You could fix that by constraining the type to be default-constructible and using Default::default(), among other things.

Ry-
  • 218,210
  • 55
  • 464
  • 476