What is a closure? (very specific)

Hi all, I'm learning deeper closures in Rust, after you have and write here the normal examples, please read it carefully, this question is closer to how Rust works, than a way to use closure. Maybe still the answer is simple and obvious, but lets keep digging.

The normal definition: Rust’s closures are anonymous functions you can save in a variable or pass as arguments to other functions.

Just that definition is far from enough to describe it.

    let mut counter = 0;
    let mut f_counter = move || {
      counter += 1;
      println!("{}", counter);
    };
    f_counter();
    f_counter();

We will get 1 and 2 as result.... so is not only picking and moving a env var to the closure, the variable inside also persist after each call... Which open a very big door in how to store and handle data... think this also put me to think... is this all? are there more hidden things?

Next point, return type...

In the example above, f_counter is a... something? I'll write its properties...

Is shows as the Trait FnMut
Can be called, a normal Trait can not be called
It can be used as a generic while a trait can not be used like that
Even if it shows as a trait, we can't use it for trait bounds, we can parse it as a type, but when want to write the trait bounds, we need to write manually exactly the same declared trait bound of the closure (yes, this can be known in compile time).

So, after all this, my conclusion is that a Closure is not a trait, in the example, f_counter is something that has implemented the FnMut trait.

To me, rn, this thing is closer to a thread with a loop waiting to run and ready to be used.

For now, I'm not very sure what a Closure is, I can use them, but would be nice to know this questions. Maybe Rust-Analyzer only shows it as a Trait because is useful know that info, even if is not the right answer.

Thx!

A closure is an anonymous struct that implements the Fn, FnMut, and/or FnOnce¹ traits. The struct members are the various variables that are captured (counter here), and the closure body is used to implement the call method.

¹ Like the other std::ops traits, these are used to overload operator syntax. In this case, it’s the function-call syntax that is customized.

13 Likes

I would love if this paragraph is in the book, very concise and explains it very well.
So seems rust-analyzer just shows the current implemented trait because is useful to know it.

I'm curious if someone known other things of it, or maybe that is all.

Thx :smiley:

1 Like

The main feature of closures is that they capture their environment. Any variables that are used in the closure body get stored in the closure value, either in the most permissible way for normal closures, or by value in move closures.

This is true. Rust-analyzer shows it as impl FnMut(), which is sometimes valid as a type, just not in let statements, at least in current Rust. It does work in function arguments:

fn call_closure(mut c: impl FnMut()) {
    c();
}

I think an example would be helpful. Your code gets translated into something like this:

let mut counter = 0;

struct CounterClosure {
    counter: i32,
}

// You aren't allowed to write these impls manually, but if you could, they
// would look something like this.
impl FnOnce<()> for CounterClosure {
    type Output = ();

    fn call_once(mut self, args: ()) -> Self::Output {
        self.counter += 1;
        println!("{}", self.counter);
    }
}

impl FnMut<()> for CounterClosure {
    fn call_mut(&mut self, args: ()) -> Self::Output {
        self.counter += 1;
        println!("{}", self.counter);
    }
}

let mut f_counter = CounterClosure { counter };

f_counter();
f_counter();
5 Likes

@drewtato's example on nightly.

Some miscellaneous notes:

  • For closures that capture borrows, it can be important to note that the borrow starts when the closure is constructed, just like constructing a nominal struct. The borrows are not delayed or reconstructed on every call. If you need that, consider making the closure take an argument instead of capturing.

  • Note that due to the signature of FnOnce, and the fact that FnMut and Fn use the same associated output type as FnOnce, it's impossible to return a borrow to a captured variable (closure field). That is, the return type cannot capture the lifetime of &mut self in FnMut or &self in Fn.[1]

  • Closures that don't capture anything can be coerced to function pointers.

  • Closures do implement some other traits when possible, like Copy and Clone.

  • The types of closures are not nameable.

  • Compiler inference of closures can be "steered" somewhat by declaring the closure in a context with Fn/FnMut/FnOnce bounds, such as a function argument. The bounds can influence how things are captured, which of the traits are implemented, and nuances about the "signature" of the closure (how it implements the Fn traits).


  1. If you think you've seen a counter-example, it's probably returning a captured borrow or reborrow of a captured borrow -- e.g. copying a captured &_ reference out from behind &self. ↩︎

4 Likes

First, let's start with the elephant in the room:

No, this is completely missing the defining point of closures. Non-closure functions (e.g. fn items and fn pointers) are values, too. If you have an fn item, you can pass it to another function as argument, or store it in a variable.

This is the whole point. A "closure" is so called because it "closes over" (captures) some of its environment. A closure, operationally, is just a bare function stored together with the variables declared outside its scope that it captures. The (anonymous) struct that stores the captured variables becomes the implicit self argument in the FnOnce::call_once(), FnMut::call_mut() or Fn::call() methods.

It's an anonymous type, implementing the trait FnMut (and its supertrait FnOnce).

I have no idea what you are saying there; "calling a trait" makes no sense. It is possible to call a specific value that implements one of the Fn* traits. The function(args) syntax is just syntax sugar for the aforementioned three methods. If you implement the function traits for your own type,[1] then instances of that type become callable too.

Again, it is completely unclear what you mean by that. All traits can be used as bounds on generic type variables.

You are probably confused by your IDE or something. Here are some facts for you:

  • A trait is not a type.
  • A type is not a trait.
  • Types can implement traits.
  • Every value has a type.
    • A type is still not a trait. So a value can't "have a type that is a trait".
  • A type that implements a trait is sometimes informally denoted as impl Trait. Whatever gave you that type hint probably says impl FnMut(...) -> _, which is the best it can show, since the type of closures has no name, as it's generated by the compiler.

You are reading way too much into this.


  1. currently only possible on nightly, but that's beside the point ↩︎

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.