Closures are a powerful piece of code present in many languages. Essentially, a closure wraps code or variables used only within the scope of the current code in a neat little package.
In its simplest form, we can have something like this:
let add = |x : i32 | x + t;
The part within the | | defines a variable called x that is only used within the scope of the calculation, and it is of type i32.
Okay, that may not seem that useful—after all, what we're doing here is adding two numbers together. Hold on though—if x is only defined within the scope of the calculation, what does x actually equal?
This is where closures come into their own. Typically, when we create a binding, we create a binding to something definite. Here, we are creating a binding, but binding it to the content of the closure. Anything between the pipes (| |) is an argument, with the expression being whatever follows the end pipe.
If you think about it, you've actually created something closer to the following:
fn add(x : i32) -> i32 { x + x }
In answer to our question "what does x actually equal?", here it is equal to the only known parameter, t. Therefore, x + t is the same as saying t + t. The add variable isn't being bound directly (that is, in the same way that we bind under normal conditions), but is borrowing the binding. This means that we have to apply the same borrowing rules as before. Say that we have the following:
let m = &mut t;
This will give the following error:
The important part of the throwback is that we're trying to borrow something that is being borrowed in an immutable line. We can fix this by changing the scope of the closure, as shown here:
let mut t = 10i32; { let add = |x : i32 | x + t; } let m = &mut t;
This will result in the error going.
With that in mind, we can start to expand on this. If the value between the pipes is the argument, then we can clearly do some interesting things with closures
Take this code, for example:
let calc = |x| { let mut result: i32 = x; result *= 4; result += 2; result -= 1; result };
Rather than create a whole new function, we use the closure and create the function inline with result and x only existing within the scope of the enclosure { }.
A closure without any arguments is the inline equivalent of the following:
fn do_something() -> T { ... }