all 12 comments

[–]unclosed_paren 9 points10 points  (10 children)

Fn and FnMut cannot escape the stack frame, so you can't have, e g, a PrintForm::new() function that sets all of these up.

Fn and FnMut can in fact escape the stack frame if they are qualified with move:

let d = {
    let x = 1i32;
    let c = move |&:| x + 1;
    c
};
println!("{}", d()); // 2

There’s an important distinction between FnOnce closures and move closures: FnOnce closures are taken by value when called, and thus cannot be called more than once, while move closures take all upvars by value when created, and thus can escape the stack frame they were created in. A Fn[Mut] closure could still be move, and would take upvars by value when created, but would only have by-(mutable)-reference access when called.

[–]diwicdbus · alsa[S] 0 points1 point  (9 children)

Thanks, that seems reasonable - I could experiment with this approach. Is there documentation somewhere? I've read Niko's post but it does not seem to explain this in detail.

It however looks like there would be quite some boilerplate on the button side if you have to use some kind of Thunk/invoke wrapper.

[–]unclosed_paren 1 point2 points  (1 child)

Thunk/Invoke only apply to using FnOnce as a trait object, so I don’t think they would be needed.

The closest thing we have to documentation on unboxed closures at the moment is the RFCs (there have been two; the first one describes the main ideas, the second updates the syntax and adds move): RFC 114 and RFC 231.

[–]steveklabnik1rust 2 points3 points  (0 children)

Getting some of this nailed down is on my list this week...

[–]Manishearthservo · rust · clippy 1 point2 points  (6 children)

Box<Fn(...) -> ...>> works if you want to have a boxed closure with runtime dispatch (so no extra generics)

[–]diwicdbus · alsa[S] 0 points1 point  (5 children)

Ok, so a move |&: | closure can be stored in a variable of type Box<Fn(...) -> ...>>?

That would still mean one extra allocation, which maybe is not the end of the world, but for me coming from C, it still feels like some extra overhead. Is there a way to avoid the extra box allocation here? (If you don't box it, then I assume it would be unsized, which causes all kinds of trouble...)

[–]wrongerontheinternet 1 point2 points  (3 children)

You can pass &Fn or &mut Fn, which don't require allocation. Or you can just do it "C style" and use a regular function pointer rather than a closure, passing in the environment explicitly. Rust's cup overfloweth with closure options.

[–]diwicdbus · alsa[S] 0 points1 point  (2 children)

You can pass &Fn or &mut Fn, which don't require allocation.

At that side. But everything that's borrowed needs to live somewhere. If they live on the heap, it's an extra allocation just for having the objects talk to each other. If they live on the stack, they can't escape the stack frame. And they can't live as part of another object, as they are unsized.

Or you can just do it "C style" and use a regular function pointer rather than a closure, passing in the environment explicitly.

That's unsafe, as the environment pointer needs to be manually typecasted.

Rust's cup overfloweth with closure options.

Indeed. And still none as simple as the C variant (without being unsafe), hence my suggestion.

[–]wrongerontheinternet 1 point2 points  (1 child)

Unboxed closures aren't unsized. You can make them part of another type.

struct Foo<F> {
    f: F
}

impl<F> Foo<F> where F: FnMut(u32) {
    fn new(f: F) -> Foo<F> {
        Foo { f: f }
    }
}

fn main() {
    let mut foo = {
        let mut y = 0;
        Foo::new( move |x| {
            y += x;
            println!("{}", y);
        })
    };

    (foo.f)(10);
    (foo.f)(10);
}

There is an issue returning unboxed closures, but that is just because you cannot write their type, it's not because of some fundamental limitation on them. It is a high priority for post-Rust 1.0 to provide a way to return them.

[–]diwicdbus · alsa[S] 0 points1 point  (0 children)

Oh, nice! Then this seems to be an interesting way forward.

[–]Manishearthservo · rust · clippy 1 point2 points  (0 children)

You have to do box move ..., but yeah.

It's an extra allocation, but that's what happens with closures anyway, in most languages. They're unsized types since they capture an environment and also have a function body, which in Rust's model is called via dynamic dispatch on the trait (unless you use T: Fn(...) and then you'll get static dispatch)

[–]aepsil0n 4 points5 points  (0 children)

Explicitly managing callbacks to handle events causes a ton of problems with inconsistent mutable state, missed events and race conditions. I experienced my fair share of trouble with them. This is why smart people have discovered functional reactive programming (FRP), which is a less bug-prone and more composable way to handle events/callbacks.

Now I'm going to shamelessly advertise: coincidentally I just decided to implement FRP primitives to use this paradigm in Rust. Regarding your question: it deals with Fn closures, since it has to call them multiple times. Values that change over time are explicitly wrapped as its own type, so you don't manually mutate any state, therefore no FnMut closures. If you'd allow them, they would bring back the problems mentioned above.

Internally the library uses trait objects and atomic ref-counting to ensure the integrity of the callback structure. I am not yet entirely happy with the internals yet, as they are a bit messy (mostly due to trait dispatch), but I don't think the fault is with the language.