Flinging some ideas around about closures

So what motivates this post is partly this reddit post, and partly the error you get when trying to do the following:

fn use_closure<F: Fn(&i32) -> &i32>(f: F) {
    let x = 42;
    f(&x);
}

fn main() {
    let closure = |x| x;
    use_closure(closure);
}

I am wondering how feasible it is to actually properly desugar closures into structs, so that the reasoning around them is clear. I think there are still a few bugs currently around lifetimes with closures (this one included) and most of these errors come from the fact that (AFAIK) it’s not a “true” desugaring.

Here’s some desugaring examples to see the kind of thing I’m proposing:

|x| x

desugars to…

struct Id;

impl<T> FnOnce<(T,)> for Id {
    type Output = T;
    extern "rust-call" fn call_once(self, (t,): (T,)) -> T {
        t
    }
}

impl<T> FnMut<(T,)> for Id {
    extern "rust-call" fn call_mut(&mut self, (t,): (T,)) -> T {
        t
    }
}

impl<T> Fn<(T,)> for Id {
    extern "rust-call" fn call(&self, (t,): (T,)) -> T {
        t
    }
}

Id
let x: i32;
|y| x + y

desugars to…

struct Add {
    x: i32,
}

impl<T> FnOnce<(T,)> for Add
    where i32: std::ops::Add<T>
{
    type Output = <i32 as std::ops::Add<T>>::Output;
    extern "rust-call" fn call_once(self, (y,): (T,)) -> Self::Output {
        self.x + y
    }
}

impl<T> FnMut<(T,)> for Add
    where i32: std::ops::Add<T>
{
    extern "rust-call" fn call_mut(&mut self, (y,): (T,)) -> Self::Output {
        self.x + y
    }
}

impl<T> Fn<(T,)> for Add
    where i32: std::ops::Add<T>
{
    extern "rust-call" fn call(&self, (y,): (T,)) -> Self::Output {
        self.x + y
    }
}

Add {
    x: x
}
|f, x| f(x, t)

desugars to…

struct Bind<T> {
    t: T,
}

impl<F, T, X> FnOnce<(F, X)> for Bind<T>
    where F: FnOnce<(X, T)>
{
    type Output = <F as FnOnce<(X, T)>>::Output;
    extern "rust-call" fn call_once(self, (f, x): (F, X)) -> Self::Output {
        f(x, self.t)
    }
}

impl<F, T, X> FnMut<(F, X)> for Bind<T>
    where F: FnOnce<(X, T)>, T: Copy
{
    extern "rust-call" fn call_mut(&mut self, (f, x): (F, X)) -> Self::Output {
        f(x, self.t)
    }
}

impl<F, T, X> Fn<(F, X)> for Bind<T>
    where F: FnOnce<(X, T)>, T: Copy
{
    extern "rust-call" fn call(&self, (f, x): (F, X)) -> Self::Output {
        f(x, self.t)
    }
}

Bind {
    t: t,
}

Another idea I had is that, once a closure has been desugared into an actual struct, it can be named. That is, if I want a struct to have a particular name instead of a compiler-generated name, I can specify that explicitly, so that I could do the following:

fn generate_id() -> Id {
    'Id |x| x
}
fn generate_add_one() -> AddOne {
    'AddOne |x| x + 1
}
fn concatenate(mut base: String) -> Concat {
    'Concat move |next| {
        base += &format!("{}", next);
        base.clone()
    }
}

fn main() {
    let mut concat = concatenate("Things: ".to_string());
    concat(42);
    concat(" is a number, ");
    concat('a');
    println!("{}", concat(" is a char.")); // prints "Things: 42 is a number, a is a char."
}

Going crazier, once a closure has been named, it can have Clone, Copy, Debug and friends automatically derived for it. So you could do the following:

fn set_new_value<T: Debug>(x: Arc<Mutex<T>>) -> ValueSetter<T> {
    #[derive(Clone)]
    'ValueSetter move |value| {
        let mut x = x.lock().unwrap();
        println!("Old value: {:?}", *x);
        *x = value;
        println!("New value: {:?}", *x);
    }
}

fn main() {
    let x = Arc::new(Mutex::new(0));
    let set = set_new_value(x);
    let set_2 = set.clone();
    thread::spawn(move || loop { set(42) });
    loop { set_2(0) }
}

You could even (I suppose) elide the #[derive(Clone)] and have it automagically derived where possible.

Anyway just some general ramblings / unrelated ideas about closures - just wanted to get some feedback from you guys if there is anything sensible coming out of my wobbly brain ^^

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.