2

I had a use-case where I wanted to store a function in an enum type

#[derive(Debug)]
enum RispErr {
    Reason(String),
}

#[derive(Clone)]
enum RispExp {
    Bool(bool),
    Symbol(String),
    Number(f64),
    List(Vec<RispExp>),
    Func(fn(&Vec<RispExp>) -> Result<RispExp, RispErr>),
}

In one case, I wanted to create a higher order function to generate these

// simplifying this for the question
// shortening this for brevity
fn make_tonicity_checker(
    tone_fn: fn(f64, f64) -> bool,
) -> impl Fn(&Vec<RispExp>) -> Result<RispExp, RispErr> {
    return move |args: &Vec<RispExp>| -> Result<RispExp, RispErr> {
        tone_fn(1.0, 2.0); // need to use this
        return Ok(RispExp::Bool(true));
    };
}

I come across errors though when I tried to use the higher order function

fn f() -> () {
    RispExp::Func(make_tonicity_checker(|a, b| a > b));
}
mismatched types

expected fn pointer, found opaque type

note: expected type `for<'r> fn(&'r std::vec::Vec<RispExp>) -> std::result::Result<RispExp, RispErr>`
         found type `impl for<'r> std::ops::Fn<(&'r std::vec::Vec<RispExp>,)>`rustc(E0308)
main.rs(93, 5): expected fn pointer, found opaque type

I dug deeper and realized that function pointers cannot capture the environment, and hence the error. I tried

Func(Fn(&Vec<RispExp>) -> Result<RispExp, RispErr>),

but, realized this fails too, as it doesn't have a size known at compile time. with some googling, I found that I could potentially pass this in as a type parameter

#[derive(Clone)]
enum RispExp<T>
where
    T: Fn(&Vec<RispExp<T>>) -> Result<RispExp<T>, RispErr>,
{
    Bool(bool),
    Symbol(String),
    Number(f64),
    List(Vec<RispExp<T>>),
    Func(T),
}

but then, in all places where I accept RispExp, I would need to provide this type parameter. That seems a bit annoying to do, because I would have to repeat where T: Fn(&Vec<RispExp<T>>) -> Result<RispExp<T>, RispErr>, everywhere.

I could do Func(Box<Fn(&Vec<RispExp>) -> Result<RispExp, RispErr>>), but then dyn fn doesn't implement Clone.

What would you recommend I do?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
Stepan Parunashvili
  • 2,627
  • 5
  • 30
  • 51
  • 2
    I don't understand why using type parameter is tought to do but instead you can `Box` the `Fn` -> `Func(Box) -> Result>),` – Ömer Erden Apr 19 '19 at 08:36
  • Thanks for the Box idea Omer. The only problem with that, is that `dyn fn` does not implement `clone`. The reason using the type parameter seemed tough to do, was because I would have to repeat that long line over and over again in a bunch of places. What would you recommend I do? – Stepan Parunashvili Apr 27 '19 at 06:57
  • 3
    What about `Func(Rc) -> Result>)`? (Or `Arc<...>` if you need `RispExp` to be `Send` and/or `Sync`) – Francis Gagné Apr 28 '19 at 03:50

1 Answers1

0

If you want to save on typing, make your type parameter bound into an alias.

type RispFunc<T> = Fn(&Vec<RispExp<T>>) -> Result<RispExp<T>, RispErr>;

enum RispExp<Func: RispFunc<Func>> {
  ...
}

Otherwise, if you use the trait object approach, you can try to implement your own clone() - How to clone a struct storing a boxed trait object? .

Isaac van Bakel
  • 1,772
  • 10
  • 22