Rust not only has named functions, but anonymous functions as well. Anonymous functions that have an associated environment are called ‘closures’, because they close over an environment. Rust has a really great implementation of them, as we’ll see.
Closures look like this:
fn main() { let plus_one = |x: i32| x + 1; assert_eq!(2, plus_one(1)); }let plus_one = |x: i32| x + 1; assert_eq!(2, plus_one(1));
We create a binding, plus_one
`plus_one, and assign it to a closure. The closure’s arguments go between the pipes (
|), and the body is an expression, in this case,
x + 1. Remember that
{ }` is an expression, so we can have multi-line
closures too:
let plus_two = |x| { let mut result: i32 = x; result += 1; result += 1; result }; assert_eq!(4, plus_two(2));
You’ll notice a few things about closures that are a bit different from regular
functions defined with fn
`fn`. The first is that we did not need to
annotate the types of arguments the closure takes or the values it returns. We
can:
let plus_one = |x: i32| -> i32 { x + 1 }; assert_eq!(2, plus_one(1));
But we don’t have to. Why is this? Basically, it was chosen for ergonomic reasons. While specifying the full type for named functions is helpful with things like documentation and type inference, the types of closures are rarely documented since they’re anonymous, and they don’t cause the kinds of error-at-a-distance problems that inferring named function types can.
The second is that the syntax is similar, but a bit different. I’ve added spaces here for easier comparison:
fn main() { fn plus_one_v1 (x: i32) -> i32 { x + 1 } let plus_one_v2 = |x: i32| -> i32 { x + 1 }; let plus_one_v3 = |x: i32| x + 1 ; }fn plus_one_v1 (x: i32) -> i32 { x + 1 } let plus_one_v2 = |x: i32| -> i32 { x + 1 }; let plus_one_v3 = |x: i32| x + 1 ;
Small differences, but they’re similar.
Closures are called such because they ‘close over their environment’. It looks like this:
fn main() { let num = 5; let plus_num = |x: i32| x + num; assert_eq!(10, plus_num(5)); }let num = 5; let plus_num = |x: i32| x + num; assert_eq!(10, plus_num(5));
This closure, plus_num
`plus_num, refers to a
letbinding in its scope:
num`. More
specifically, it borrows the binding. If we do something that would conflict
with that binding, we get an error. Like this one:
let mut num = 5; let plus_num = |x: i32| x + num; let y = &mut num;
Which errors with:
error: cannot borrow `num` as mutable because it is also borrowed as immutable
let y = &mut num;
^~~
note: previous borrow of `num` occurs here due to use in closure; the immutable
borrow prevents subsequent moves or mutable borrows of `num` until the borrow
ends
let plus_num = |x| x + num;
^~~~~~~~~~~
note: previous borrow ends here
fn main() {
let mut num = 5;
let plus_num = |x| x + num;
let y = &mut num;
}
^
A verbose yet helpful error message! As it says, we can’t take a mutable borrow
on num
`num` because the closure is already borrowing it. If we let the closure go
out of scope, we can:
let mut num = 5; { let plus_num = |x: i32| x + num; } // plus_num goes out of scope, borrow of num ends let y = &mut num;
If your closure requires it, however, Rust will take ownership and move the environment instead. This doesn’t work:
fn main() { let nums = vec![1, 2, 3]; let takes_nums = || nums; println!("{:?}", nums); }let nums = vec![1, 2, 3]; let takes_nums = || nums; println!("{:?}", nums);
We get this error:
note: `nums` moved into closure environment here because it has type
`[closure(()) -> collections::vec::Vec<i32>]`, which is non-copyable
let takes_nums = || nums;
^~~~~~~
Vec<T>
`Vechas ownership over its contents, and therefore, when we refer to it in our closure, we have to take ownership of
nums. It’s the same as if we’d passed
nums` to a function that took ownership of it.
move
`move` closuresWe can force our closure to take ownership of its environment with the move
`move`
keyword:
let num = 5; let owns_num = move |x: i32| x + num;
Now, even though the keyword is move
`move, the variables follow normal move semantics. In this case,
5implements
Copy, and so
`, and so owns_num
`owns_numtakes ownership of a copy of
num`. So what’s the difference?
let mut num = 5; { let mut add_num = |x: i32| num += x; add_num(5); } assert_eq!(10, num);
So in this case, our closure took a mutable reference to num
`num, and then when we called
add_num, it mutated the underlying value, as we’d expect. We also needed to declare
add_numas
` as mut
`mut` too, because we’re mutating its
environment.
If we change to a move
`move` closure, it’s different:
let mut num = 5; { let mut add_num = move |x: i32| num += x; add_num(5); } assert_eq!(5, num);
We only get 5
`5. Rather than taking a mutable borrow out on our
num`, we took
ownership of a copy.
Another way to think about move
`moveclosures: they give a closure its own stack frame. Without
move, a closure may be tied to the stack frame that created it, while a
moveclosure is self-contained. This means that you cannot generally return a non-
move` closure from a function, for example.
But before we talk about taking and returning closures, we should talk some more about the way that closures are implemented. As a systems language, Rust gives you tons of control over what your code does, and closures are no different.
Rust’s implementation of closures is a bit different than other languages. They are effectively syntax sugar for traits. You’ll want to make sure to have read the traits chapter before this one, as well as the chapter on trait objects.
Got all that? Good.
The key to understanding how closures work under the hood is something a bit
strange: Using ()
`()to call a function, like
foo()`, is an overloadable
operator. From this, everything else clicks into place. In Rust, we use the
trait system to overload operators. Calling functions is no different. We have
three separate traits to overload with:
pub trait Fn<Args> : FnMut<Args> { extern "rust-call" fn call(&self, args: Args) -> Self::Output; } pub trait FnMut<Args> : FnOnce<Args> { extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output; } pub trait FnOnce<Args> { type Output; extern "rust-call" fn call_once(self, args: Args) -> Self::Output; }
You’ll notice a few differences between these traits, but a big one is self
`self:
`:
Fn
`Fntakes
` takes &self
`&self,
`, FnMut
`FnMuttakes
` takes &mut self
, and FnOnce
`FnOncetakes
` takes self
`self. This covers all three kinds of
self` via the usual method call syntax. But we’ve
split them up into three traits, rather than having a single one. This gives us
a large amount of control over what kind of closures we can take.
The || {}
`|| {}syntax for closures is sugar for these three traits. Rust will generate a struct for the environment,
impl` the appropriate trait, and then
use it.
Now that we know that closures are traits, we already know how to accept and return closures: just like any other trait!
This also means that we can choose static vs dynamic dispatch as well. First, let’s write a function which takes something callable, calls it, and returns the result:
fn main() { fn call_with_one<F>(some_closure: F) -> i32 where F : Fn(i32) -> i32 { some_closure(1) } let answer = call_with_one(|x| x + 2); assert_eq!(3, answer); }fn call_with_one<F>(some_closure: F) -> i32 where F : Fn(i32) -> i32 { some_closure(1) } let answer = call_with_one(|x| x + 2); assert_eq!(3, answer);
We pass our closure, |x| x + 2
, to call_with_one
. It just does what it
suggests: it calls the closure, giving it 1
`1` as an argument.
Let’s examine the signature of call_with_one
in more depth:
fn call_with_one<F>(some_closure: F) -> i32
We take one parameter, and it has the type F
`F. We also return a
i32`. This part
isn’t interesting. The next part is:
where F : Fn(i32) -> i32 {
Because Fn
`Fnis a trait, we can bound our generic with it. In this case, our closure takes a
i32as an argument and returns an
i32, and so the generic bound we use is
Fn(i32) -> i32`.
There’s one other key point here: because we’re bounding a generic with a trait, this will get monomorphized, and therefore, we’ll be doing static dispatch into the closure. That’s pretty neat. In many languages, closures are inherently heap allocated, and will always involve dynamic dispatch. In Rust, we can stack allocate our closure environment, and statically dispatch the call. This happens quite often with iterators and their adapters, which often take closures as arguments.
Of course, if we want dynamic dispatch, we can get that too. A trait object handles this case, as usual:
fn main() { fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 { some_closure(1) } let answer = call_with_one(&|x| x + 2); assert_eq!(3, answer); }fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 { some_closure(1) } let answer = call_with_one(&|x| x + 2); assert_eq!(3, answer);
Now we take a trait object, a &Fn
`&Fn. And we have to make a reference to our closure when we pass it to
call_with_one, so we use
&||`.
It’s very common for functional-style code to return closures in various situations. If you try to return a closure, you may run into an error. At first, it may seem strange, but we’ll figure it out. Here’s how you’d probably try to return a closure from a function:
fn main() { fn factory() -> (Fn(i32) -> i32) { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer); }fn factory() -> (Fn(i32) -> i32) { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer);
This gives us these long, related errors:
error: the trait `core::marker::Sized` is not implemented for the type
`core::ops::Fn(i32) -> i32` [E0277]
fn factory() -> (Fn(i32) -> i32) {
^~~~~~~~~~~~~~~~
note: `core::ops::Fn(i32) -> i32` does not have a constant size known at compile-time
fn factory() -> (Fn(i32) -> i32) {
^~~~~~~~~~~~~~~~
error: the trait `core::marker::Sized` is not implemented for the type `core::ops::Fn(i32) -> i32` [E0277]
let f = factory();
^
note: `core::ops::Fn(i32) -> i32` does not have a constant size known at compile-time
let f = factory();
^
In order to return something from a function, Rust needs to know what
size the return type is. But since Fn
`Fnis a trait, it could be various things of various sizes: many different types can implement
Fn`. An easy
way to give something a size is to take a reference to it, as references
have a known size. So we’d write this:
fn factory() -> &(Fn(i32) -> i32) { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer);
But we get another error:
error: missing lifetime specifier [E0106]
fn factory() -> &(Fn(i32) -> i32) {
^~~~~~~~~~~~~~~~~
Right. Because we have a reference, we need to give it a lifetime. But
our factory()
function takes no arguments, so elision doesn’t kick in
here. What lifetime can we choose? 'static
`'static`:
fn factory() -> &'static (Fn(i32) -> i32) { let num = 5; |x| x + num } let f = factory(); let answer = f(1); assert_eq!(6, answer);
But we get another error:
error: mismatched types:
expected `&'static core::ops::Fn(i32) -> i32`,
found `[closure <anon>:7:9: 7:20]`
(expected &-ptr,
found closure) [E0308]
|x| x + num
^~~~~~~~~~~
This error is letting us know that we don’t have a &'static Fn(i32) -> i32
,
we have a [closure <anon>:7:9: 7:20]
. Wait, what?
Because each closure generates its own environment struct
`structand implementation of
Fnand friends, these types are anonymous. They exist just solely for this closure. So Rust shows them as
closure
But why doesn’t our closure implement &'static Fn
? Well, as we discussed before,
closures borrow their environment. And in this case, our environment is based
on a stack-allocated 5
`5, the
`, the num
`num` variable binding. So the borrow has a lifetime
of the stack frame. So if we returned this closure, the function call would be
over, the stack frame would go away, and our closure is capturing an environment
of garbage memory!
So what to do? This almost works:
fn factory() -> Box<Fn(i32) -> i32> { let num = 5; Box::new(|x| x + num) } fn main() { let f = factory(); let answer = f(1); assert_eq!(6, answer); }fn factory() -> Box<Fn(i32) -> i32> { let num = 5; Box::new(|x| x + num) } let f = factory(); let answer = f(1); assert_eq!(6, answer);
We use a trait object, by Box
`Boxing up the
Fn`. There’s just one last problem:
error: closure may outlive the current function, but it borrows `num`,
which is owned by the current function [E0373]
Box::new(|x| x + num)
^~~~~~~~~~~
We still have a reference to the parent stack frame. With one last fix, we can make this work:
fn factory() -> Box<Fn(i32) -> i32> { let num = 5; Box::new(move |x| x + num) } fn main() { let f = factory(); let answer = f(1); assert_eq!(6, answer); }fn factory() -> Box<Fn(i32) -> i32> { let num = 5; Box::new(move |x| x + num) } let f = factory(); let answer = f(1); assert_eq!(6, answer);
By making the inner closure a move Fn
, we create a new stack frame for our
closure. By Box
`Box`ing it up, we’ve given it a known size, and allowing it to
escape our stack frame.