[Blog] Closures: Magic Functions

I made a blog post explaining how closures are desugared and how they operate because I found quite a few questions here on users related to closures.

15 Likes

This is a great explanation of how closures work!

One critique of your blog: it's not always clear which Playground link goes with which code block. You've got

some(code, version_1)

An explanation of how the code is structured, what it means, and how it relates to Closures: The Magic Functions.

The explanatory text can be long, sometimes enough that you can even end up scrolling a complete screen-full.

Playground Link


some(code, version_2)

Some more explanatory paragraphs about some code version 2 and how it is interesting and complicated.

It's not clear whether the Playground Link goes with version_2, which it's physically closer to, or version_1, which it is in the same logical section as because of the dividing line.

1 Like

Ok, I'll annotate them. Thank you for the feedback!

Typo:

enviornment

Otherwise, good post! The rules around when closures act certain ways can be confusing, so it's nice to see them laid out so clearly.

1 Like

Glad we also covered it in our Vancouver Rust Meetup

Incredible post!
I found this very illuminating, while also short and concise.

1 Like

Also - I've come back to this article a couple times I got stuck and each time it explained the next thing I got stuck on :wink:

Should definitely be on an essential reading list somewhere

2 Likes

Thanks!
It's a little confusing with Playground Link closure_2 , which has FnOnce, FnMut, and Fn. But the code in the post only has Fn.
Could you explain why there are actually FnOnce and FnMut. And when are they used?

I know you mentioned:

I will not show the impls for all three Fn* traits, only the most specific one. So if you see Fn , then assume FnMut and FnOnce are impled with the same function body. If you see FnMut , then assume that FnOnce is impled with the same function body, but Fn is not impled. If you see FnOnce , then assume that Fn and FnMut are not impled. I will also put type Output in a comment to show what it would be if I only impl Fn or FnMut .

But an explanation of "why there are actually FnOnce and FnMut. And when are they used" would be still helpful.

You need to implement FnMut in order to implement Fn and you need to implement FnOnce in order to implement FnMut because of their trait definitions.

Let's look at those definitions (comments are mine)

#[lang = "fn_once"]
#[must_use = "closures are lazy and do nothing unless called"]
pub trait FnOnce<Args> {
    type Output; // notice how Output is only defined here
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

#[lang = "fn_mut"]
#[must_use = "closures are lazy and do nothing unless called"]
                    // notice the bounds here
pub trait FnMut<Args>: FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

#[lang = "fn"]
#[must_use = "closures are lazy and do nothing unless called"]
                 // notice the bounds here
pub trait Fn<Args>: FnMut<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

These bounds force the implementations that I omitted in the blog post, but included in the playground example.

But do note that it is possible to give different implementations for each trait, currently no stable type does this and it is discouraged because it will be confusing. However, you can use this property to see when specific implementations are called by printing different things for each implementation.

1 Like