Closures that refer to each other?

In Lisp, we can write the following:

(let [even? (fn [n] (if (== n 0) true (not (odd? (- n 1))))
      odd (fn [n] (if (== n 1) true (not (even? (- n 1)))))]
  ...)
===

In Rust, we can write _global functions_ that call each other.

However, is it possible to create two closures that call each other. (I mean "directly" call each other, not via some 'call this function in a refcell' type indirection).

This would be impossible to instantiate because of lifetimes, and the fact that this would create a self-referential struct.
Why?

struct MyClosure(RefTo<MyClosure>);
impl Fn<(usize,)> for MyClosure {
    type Output = usize;
    extern "rust-call" fn call(&self, (x,): (usize,)) -> usize {
        x * 2
    }
}

Say we could create an instance of MyClosure named A and an instance of MyClosure named B. I would be able to say (A.0).0 and end up at A again, which is a self-referential struct:

fn foo(&self: &MyClosure) {
    (self.0) //This is RefTo<MyClosure>, we're ignoring references for now.
        .0   //This now refers to `self`, so moving `self` will invalidate
             //both `self` and whatever `self.0` points to.
}

Do note as well, that making your closures take the other as a parameter would be perfectly viable using a custom-Fn because that would allow us to refer to self where self is the closure.


Also

Please note that closures, specifically ones that capture variables, either by reference or value, are objects, that need to be instantiated. Closures that do not capture or regular functions are simply functions, they are places in memory where instructions are kept, not objects, and a fn() -> () is a function pointer, which only points to a function, and doesn’t contain an object or attached data.

1 Like

Basic example, with pure functions:

fn main ()
{
    type Int = u32;

    fn is_even (n: Int) -> bool
    {
        trace_fn! { "is_even({})", n => 
            n == 0 || is_odd(n - 1) // FUNCTION BODY
        }
    }
    
    fn is_odd (n: Int) -> bool
    {
        trace_fn! { "is_odd({})", n => 
            n != 0 && is_even(n - 1) // FUNCTION BODY
        }
    }

    dbg!(is_even(5));
}

prints

is_even(5)?
|   is_odd(4)?
|   |   is_even(3)?
|   |   |   is_odd(2)?
|   |   |   |   is_even(1)?
|   |   |   |   |   is_odd(0)?
|   |   |   |   |   +-> false
|   |   |   |   +-> false
|   |   |   +-> false
|   |   +-> false
|   +-> false
+-> false
[src/main.rs:47] is_even(5) = false

Regarding an example with closures, this, as @OptimisticPeach said, won’t be possible in general, due to it requiring two “objects” that refer to each other, thus implying self-referentiability which, as we know, is a problematic pattern in Rust.

However, this can be avoided by using static closures (it requires naming the type of a closure, which in stable can only be done with environment-less closures by coercing them to fn() pointers), since a static is not part of a closure’s environment:

#[allow(non_upper_case_globals)]
fn main ()
{
    type Int = u32;

    static is_even: fn(Int) -> bool = |n| {
        trace_fn! { "is_even({})", n => 
            n == 0 || is_odd(n - 1)
        }
    };
    
    static is_odd: fn(Int) -> bool = |n| {
        trace_fn! { "is_odd({})", n => 
            n != 0 && is_even(n - 1)
        }
    };

    dbg!(is_even(14));
}

Lastly, and this one is the most interesting, is that since a closure is just an object with a method, we can avoid the self-referentiability issue by having one “object” with two methods (we lose some sugar but it remains fine).

(In my example, is_even and is_odd will be sharing a reference to an out_stream:

type Int = u32;

/// Our "double closure" trait
trait EvenTheOdds {
    fn is_even (&mut self, n: Int) -> bool;
    fn is_odd  (&mut self, n: Int) -> bool;
}

struct TraceFn<W : Write> {
    out_stream: W,
}

impl<W : Write> EvenTheOdds for TraceFn<W> {
    fn is_even (&mut self, n: Int) -> bool
    {
        trace_fn! { using &mut self.out_stream, "is_even({})", n =>
            n == 0 || self.is_odd(n - 1)
        }
    }
    
    fn is_odd (&mut self, n: Int) -> bool
    {
        trace_fn! { using &mut self.out_stream, "is_odd({})", n =>
            n != 0 && self.is_even(n - 1)
        }
    }
}

fn main ()
{
    let mut stderr_tracer = TraceFn { out_stream: io::stderr() };
    
    dbg!(stderr_tracer.is_even(14));
}
1 Like