Closures

CIS 198 Lecture 4


Closures

  • A closure, anonymous function, or lambda function is a common paradigm in functional languages.
  • In Rust, they’re fairly robust, and match up well with the rest of Rust’s ownership model.
1. let square = |x: i32| -> i32 { x * x };
2. println!("{}", square(3));
3. // => 6

???

Inline function definitions which can be bound to variables. The function block is executed when the closure is called.


Closure Syntax

1. let foo_v1 = |x: i32| { x * x };
2. let foo_v2 = |x: i32, y: i32| x * y;
3. let foo_v3 = |x: i32| {
4. // Very Important Arithmetic
5. let y = x * 2;
6. let z = 4 + y;
7. x + y + z
8. };
9. let foo_v4 = |x: i32| if x == 0 { 0 } else { 1 };
  • These look pretty similar to function definitions.
  • Specify arguments in ||, followed by the return expression.
  • The return expression can be a series of expressions in {}.

???

  • let instead of fn
  • Arguments in pipes
  • Braces are optional

Type Inference

1. let square_v4 = |x: u32| { (x * x) as i32 };

3. let square_v4 = |x| -> i32 { x * x }; // ← unable to infer enough
4. let square_v4 = |x|        { x * x }; // ← type information!
  • Unlike functions, we don’t need to specify the return type or argument types of a closure.
  • In this case, the compiler can’t infer the type of the argument x from the return expression x * x.

???

Having concrete function types for type inference and self-documentation. For closures, ease of use is more important.


Closure Environment

  • Closures close over (contain) their environment.
1. let magic_num = 5;
2. let magic_johnson = 32;
3. let plus_magic = |x: i32| x + magic_num;
  • The closure plus_magic is able to reference magic_num even though it’s not passed as an argument.
  • magic_num is in the “environment” of the closure.
  • magic_johnson is not borrowed!

Closure Environment

  • If we try to borrow magic_num in a conflicting way after the closure is bound, we’ll get an error from the compiler:
1. let mut magic_num = 5;
2. let magic_johnson = 32;
3. let plus_magic = |x: i32| x + magic_num;

5. let more_magic = &mut magic_num; // Err!
6. println!("{}", magic_johnson); // Ok!
1. error: cannot borrow `magic_num` as mutable because it is
2. already borrowed as immutable

4. [...] the immutable borrow prevents subsequent moves or mutable
5. borrows of `magic_num` until the borrow ends
  • Why? plus_magic borrows magic_num when it closes over it!
  • However, magic_johnson is not used in the closure, and its ownership is not affected.

Closure Environment

  • We can fix this kind of problem by making the closure go out of scope:
1. let mut magic_num = 5;
2. {
3. let plus_magic = |x: i32| x + magic_num;
4. } // the borrow of magic_num ends here

6. let more_magic = &mut magic_num; // Ok!
7. println!("magic_num: {}", more_magic);

???

Questions?


Move Closures

  • As usual, closures are choose-your-own-~~adventure~~ ownership.
  • Sometimes it’s not okay to have a closure borrow anything.
  • You can force a closure to take ownership of all environment variables by using the move keyword.
  • “Taking ownership” can mean taking a copy, not just moving.
1. let mut magic_num = 5;
2. let own_the_magic = move |x: i32| x + magic_num;
3. let more_magic = &mut magic_num;

Move Closures

  • move closures are necessary when the closure f needs to outlive the scope in which it was created.
  • e.g. when you pass f into a thread, or return f from a function.
  • move essentially disallows bringing references into the closure.
1. fn make_closure(x: i32) -> Box<Fn(i32) -> i32> {
2. let f = move |y| x + y; // ^ more on this in 15 seconds
3. Box::new(f)
4. }

6. let f = make_closure(2);
7. println!("{}", f(3));

Closure Ownership

  • Sometimes, a closure must take ownership of an environment variable to be valid. This happens automatically (without move):

  • If the value is moved into the return value.

    1. let lottery_numbers = vec![11, 39, 51, 57, 75]; 2. { 3. let ticket = || { lottery_numbers }; 4. } 5. // The braces do no good here. 6. println!("{:?}", lottery_numbers); // use of moved value - Or moved anywhere else.

    1. let numbers = vec![2, 5, 32768]; 2. let alphabet_soup = || { numbers; vec!['a', 'b'] }; 3. // ^ throw away unneeded ingredients 4. println!("{:?}", numbers); // use of moved value - If the type is not Copy, the original variable is invalidated.


Closure Ownership

1. let numbers = vec![2, 5, 32768];
2. let alphabet_soup = || { numbers; vec!['a', 'b'] };
3. // ^ throw away unneeded ingredients
4. alphabet_soup();
5. alphabet_soup(); // use of moved value
  • Closures which own data and then move it can only be called once.
  • move behavior is implicit because alphabet_soup must own numbers to move it.
1. let numbers = vec![2, 5, 32768];
2. let alphabet_soup = move || { println!("{:?}", numbers) };
3. alphabet_soup();
4. alphabet_soup(); // Delicious soup
  • Closures which own data but don’t move it can be called multiple times.

Closure Ownership

  • The same closure can take some values by reference and others by moving ownership (or Copying values), determined by behavior.

Closure Traits

  • Closures are actually based on a set of traits under the hood!
  • Fn, FnMut, FnOnce - method calls are overloadable operators.
1. pub trait Fn<Args> : FnMut<Args> {
2. extern "rust-call"
3. fn call(&self, args: Args) -> Self::Output;
4. }

6. pub trait FnMut<Args> : FnOnce<Args> {
7. extern "rust-call"
8. fn call_mut(&mut self, args: Args) -> Self::Output;
9. }

11. pub trait FnOnce<Args> {
12. type Output;

14. extern "rust-call"
15. fn call_once(self, args: Args) -> Self::Output;
16. }

Closure Traits

  • These traits all look pretty similar, but differ in the way they take self:
  • Fn borrows self as &self
  • FnMut borrows self mutably as &mut self
  • FnOnce takes ownership of self
  • Fn is a superset of FnMut, which is a superset of FnOnce.
  • Functions also implement these traits.

“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.”¹

¹Taken from the Rust Book


Closures As Arguments

  • Passing closures works like function pointers.
  • Let’s take a (simplified) look at Rust’s definition for map¹.
1. // self = Vec<A>
2. fn map<A, B, F>(self, f: F) -> Vec<B>
3. where F: FnMut(A) -> B;
  • map takes an argument f: F, where F is an FnMut trait object.
  • You can pass regular functions in, since the traits line up!

¹Real map coming in next lecture.


Returning Closures

  • You may find it necessary to return a closure from a function.
  • Unfortunately, since closures are implicitly trait objects, they’re unsized!
1. fn i_need_some_closure() -> (Fn(i32) -> i32) {
2. let local = 2;
3. |x| x * local
4. }
1. error: the trait `core::marker::Sized` is not implemented
2. for the type `core::ops::Fn(i32) -> i32 + 'static`
  • An Fn object is not of constant size at compile time.
  • The compiler cannot properly reason about how much space to allocate for the Fn.

Returning Closures

  • Okay, we can fix this! Just wrap the Fn in a layer of indirection and return a reference!
1. fn i_need_some_closure_by_reference() -> &(Fn(i32) -> i32) {
2. let local = 2;
3. |x| x * local
4. }
1. error: missing lifetime specifier
  • Now what? We haven’t given this closure a lifetime specifier…
  • The reference we’re returning must outlive this function.
  • But it can’t, since that would create a dangling pointer.

Returning Closures

  • What’s the right way to fix this? Use a Box!
1. fn box_me_up_that_closure() -> Box<Fn(i32) -> i32> {
2. let local = 2;
3. Box::new(|x| x * local)
4. }
1. error: closure may outlive the current function, but it
2. borrows `local`, which is owned by the current function [E0373]
  • Augh! We were so close!
  • The closure we’re returning is still holding on to its environment.
  • That’s bad, since once box_me_up_that_closure returns, local will be destroyed.

Returning Closures

  • The good news? We already know how to fix this:
1. fn box_up_your_closure_and_move_out() -> Box<Fn(i32) -> i32> {
2. let local = 2;
3. Box::new(move |x| x * local)
4. }
  • And you’re done. It’s elementary!