Closures
We can't nest functions in Java or C, but we can in Rust. This program nests function enclose inside of main:
fn main() {
fn enclose(x: i32) -> String {
let open = '(';
let close = ')';
format!("{}{}{}", open, x, close)
}
println!("{}", enclose(14));
}
fn main() {
fn enclose(x: i32) -> String {
let open = '(';
let close = ')';
format!("{}{}{}", open, x, close)
}
println!("{}", enclose(14));
}There are two compelling reasons for a programming language to allow one function to nest inside another. The first reason is to narrow the nested function's scope. If a function has only local utility, then a local scope is more appropriate than a top-level scope. The second reason is to gain closure semantics—which means the function may access values from the surrounding context without us having to pass them as parameters.
Do nested functions in Rust have closure semantics? We can check by migrating the delimiters of enclose out to main:
fn main() {
let open = '(';
let close = ')';
fn enclose(x: i32) -> String {
format!("{}{}{}", open, x, close)
}
println!("{}", enclose(14));
}
fn main() {
let open = '(';
let close = ')';
fn enclose(x: i32) -> String {
format!("{}{}{}", open, x, close)
}
println!("{}", enclose(14));
}The compiler rejects this code with the error message can't capture dynamic environment in a fn item. Rust doesn't give nested functions closure semantics. If we want closure semantics, we must use a lambda form instead of a function definition:
fn main() {
let open = '(';
let close = ')';
let enclose = |x: i32| -> String {
format!("{}{}{}", open, x, close)
};
println!("{}", enclose(14)); // (14)
}
fn main() {
let open = '(';
let close = ')';
let enclose = |x: i32| -> String {
format!("{}{}{}", open, x, close)
};
println!("{}", enclose(14)); // (14)
}In some languages, we call an anonymous function like this a lambda. The Rust documentation doesn't use this term. Lambdas in Rust have access to variables from the surrounding scope, and we emphasize this by calling it a closure. A closure without parameters has an empty || for its parameter list. The arrow and return type are omitted when it doesn't return a value. The curly braces are optional when the body is a single statement or expression.
Closures and Memory
Closures may not seem relevant in a discussion of memory management in Rust. But they are included here because their interactions with the free variables from the surrounding context must satisfy Rust's borrow checker. In the closure above, open and close are implicitly borrowed. Suppose they are declared as mutable, and the closure's lifetime spans across their mutations, as in this code:
fn main() {
let mut open = '(';
let mut close = ')';
let enclose = |x: i32| -> String {
format!("{}{}{}", open, x, close)
};
println!("{}", enclose(14));
open = '[';
close = ']';
println!("{}", enclose(15));
}
fn main() {
let mut open = '(';
let mut close = ')';
let enclose = |x: i32| -> String {
format!("{}{}{}", open, x, close)
};
println!("{}", enclose(14));
open = '[';
close = ']';
println!("{}", enclose(15));
}This code does not compile. The mutable owners in main and the borrowers in enclose cannot coexist. There's not really an easy fix. If we have a closure that relies on mutable state, we should probably turn the state into a struct and turn the closure into a method of the struct that can freely access the state.
If the closure itself needs to mutate the free variables, then both the closure and the free variables must be declared mutable. This code creates a free string variable and a closure that appends to the string:
let mut danger = String::from("Danger");
let mut elevate = || {
danger.push_str("!");
};
elevate();
elevate();
elevate();
println!("{}", danger); // Danger!!!
let mut danger = String::from("Danger");
let mut elevate = || {
danger.push_str("!");
};
elevate();
elevate();
elevate();
println!("{}", danger); // Danger!!!This compiles and runs just fine even though there's a mutable owner and a mutable borrower of the string. It works because the closure's implicit borrower becomes inactive after the elevate calls. The compiler only rejects the code if the owner and borrower are active at the same time, as in this code:
let mut danger = String::from("Danger");
let mut elevate = || {
danger.push_str("!");
};
println!("{}", danger); // compile error
elevate();
elevate();
elevate();
println!("{}", danger);
let mut danger = String::from("Danger");
let mut elevate = || {
danger.push_str("!");
};
println!("{}", danger); // compile error
elevate();
elevate();
elevate();
println!("{}", danger);The compiler rejects the first println!. It recognizes that a mutable borrower has already been given to elevate and that the borrower is still needed for the upcoming elevate calls.
We generally will not encounter mutability issues if we only define closures for their most common use case: passing anonymous functions to higher-order functions.