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.
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.
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.
Glad we also covered it in our Vancouver Rust Meetup
Incredible post!
I found this very illuminating, while also short and concise.
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 ![]()
Should definitely be on an essential reading list somewhere
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 seeFn, then assumeFnMutandFnOnceare impled with the same function body. If you seeFnMut, then assume thatFnOnceis impled with the same function body, butFnis not impled. If you seeFnOnce, then assume thatFnandFnMutare not impled. I will also puttype Outputin a comment to show what it would be if I only implFnorFnMut.
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.