Closure that refers to itself

#1
  1. I realize this sounds like a terrible idea, but I’m dealing with translating some Haskell Parsec Code (and we know how much Haskell loves tail recursion.)

2.Is there a way to define a closure that refers to itself? If so, what’s a minimal example? This is something of the form:

l.et x = |...| {
  we have to call some other function (passing it x);
}

Is this possible? safe? Something intuitively seems wrong, but I can’t figure out what.

0 Likes

#2

I don’t think it’s possible with closures.
You may want to check loop_fn from futures. The documentation has a good example.
https://docs.rs/futures/0.1.25/futures/future/fn.loop_fn.html

1 Like

#3

That would be a self-referential struct, so nope.

You could create a closure that calls a regular recursive function with a context argument.

1 Like

#4

@naim , @kornel : let’s see if I understand this correctly:

  1. functions do not have attached state

  2. closures CAN have attached state

  3. we can’t have self referencing structs. therefore we can not have self referencing closures

  4. however, we can have self referencing functions, and these functions can have type signature

fn blah(state: State, ...)

Is this correct?

1 Like

#5

You can do it, but it would require you to desugar closures manually. You can read my blog on closure desugaring to get a start.

Then you can manually desugar the closure and do that sort of recuesion.

2 Likes

#6
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let self_: Rc<RefCell<Box<Fn(u32)>>> = Rc::new(RefCell::new(Box::new(|_| unreachable!())));
    *self_.borrow_mut() = Box::new({
        let self_ = self_.clone();
        move |n: u32| {
            if n < 3 {
                print!("Tick... ");
                (self_.borrow())(n+1);
            } else {
                println!("Boom!");
            }
        }
    });
    
    (self_.borrow())(0);
}

(Playpen)

2 Likes

#7

@DanielKeep this creates a cycle thus leaking memory, right? Using a rc::Weak to make the recursive call should solve the issue (i.e., let self_ = Rc::downgrade(&self_) + (self_.try_upgrade().unwrap().borrow())(n + 1)).

2 Likes

#8

That is really well explained in terms of Tick... Tick... Tick... Boom! as that is what seems is going to end up happening in any case.


Here is an implementation of what would probably result with @KrishnaSannasi’s custom Fn* types.

3 Likes

#9

Since Fn desugaring is unstable, I’d advise to use the exact same code that you would for that, but to replace afterwards impl Fn for ... by an inherent impl impl .... Then, instead of writing f() you would (just) have to write f.call(), which is not that big of a price to pay for stable-compatible code.

3 Likes

#10

@DanielKeep 's use of Refcell reminds me of https://github.com/koute/stdweb/blob/dff1e06086124fe79e3393a99ae8e2d424f5b2f1/examples/webgl/src/main.rs#L131 where we need some callback (with state) to be called at 60fps.

0 Likes