Skip to content

Wrote part of Ch 13, closures and iterators #381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 129 additions & 4 deletions src/ch13-00-functional-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,145 @@

### What is a closure

How are they diff from fns
In programming languages, a closure is a lot like a function. Like a function, closures contain code
that is executed when the closure is called. The main difference, besides syntax, between closures
and functions is that closures have *capture*. What this means is that a closure can use variables
in its surrounding scope. Consider the following code:

```rust,ignore
fn main() {
let x = 4;
fn is_x(z: i32) -> bool {
z == x
}

let y = 4;
is_x(y);
}
```

Here, the function `is_x` is trying to use the x from `main`'s scope. This doesn't work, however,
giving the error

```text
error: can't capture dynamic environment in a fn item; use the || { ... } closure form instead [E0434]
z == x
^
```

This error message is saying that functions can't *capture* -- only closures can. So, let's try
making the `is_x` function into a closure:
```rust
fn main() {
let x = 4;
let is_x = |z: i32| -> bool {
z == x
};

let y = 4;
is_x(y);
}
```

We can see here that the syntax for defining a closure is `|input arguments| -> output { code }`.
This is very similar to a function definition with some major differences. The name is not a part of
the closure -- by default, closures are unnamed. We also use vertical bars (`|`) instead of
parentheses to define the arguments to the closure, and specifying the types of the inputs and
outputs is optional if Rust can figure them out. Finally, closures are expressions rather than
statements. You can see here that we could assign the closure to a variable, and needed to terminate
the line with a semicolon. Frequently, closures are defined and passed into functions without ever
giving them names.

This closure is capturing the x variable. But how is it doing that? What if we define a closure in a
function and return it? What if we change x between where the closure is defined and where it's
executed?

By default, closures capture the variables by reference. This means that closures cannot outlive
variables that they capture. If we try to compile the following code:
```rust,ignore
fn main() {
let closure;
{
let x = 4;
closure = ||{ x }; // Closure that takes no arguments and returns x
}
}
```
we get an error because `x` does not live long enough.

We can make closures move (or copy, for types declared Copy) their values in by using the `move`
keyword:
```rust
fn main() {
let closure;
{
let x = 4;
closure = move ||{ x }; // Closure that takes no arguments and returns x
}
}
```

### `Fn` traits

It's clear that closures are more than functions, because closures have to keep track of what
variables they've captured. Closures are essentially functions that come with a struct that includes
their captured variables (or references). This means that every closure is a different type. If we
want a function to take a closure as an argument, we can't simply talk about a "closure" type,
because each one is a different type. The way that Rust manages this is with traits. There are three
traits that can be automatically applied to each closure, depending on what the closure is doing:
`FnOnce`, `FnMut`, and `Fn`. These traits derive from each other, so if a type is `FnMut`, it must
also be `FnOnce`, and if a type is `Fn`, it must be both `FnMut` and `FnOnce`. Closures
automatically derive from the ones that are appropriate, and you cannot currently derive them for
your custom types.

If you want to write a function such as `map` that takes in a function, you should almost always
take in one of the `Fn` traits. The `FnOnce` trait defines a function `call_once` that consumes
`self`. It's the most general option, and if your function will only call the given function once,
it should take in an `FnOnce`. `FnMut` is the next most general, since its `call_mut` function takes
`self` by mutable reference, so you need a mutable reference to the closure to call it. `Fn` is the
most specific of these, but you only need a immutable reference to call a function that is `Fn`.

All functions and closures implement `FnOnce`. If a closure takes its variables by reference rather
than by move, then it also implements `FnMut`. If the closure modifies none of its captures, then it
also implements `Fn`. All functions also implement `Fn` because they don't capture at all.

## Iterators

Iterators are types that implement the `Iterator` trait. Iterators are designed to return a sequence
of values by repeatedly calling their `next()` method. Often, these values come from a data
structure such as `Vec`. Iterators are powerful, however, because they can be used for more than
that. For example, you can have an infinite iterator -- one whose `next()` method never returns
`None`. There are also functions on iterators like `map`, which applies a function to each value in
the iterators as they're requested. `map` has low memory usage because it only applies the function
as elements are requested, so the whole sequence does not need to build up.

### Iterator & for loop

.into_iter()
`for` loops are usually said to "iterate" over things, so it makes perfect sense that in Rust, for
loops use iterators. Specifically, you can say `for x in y` if `y` implements the `IntoIterator`
trait with the `into_iter` method. This method consumes `self` and (for data structures) returns an
iterator that returns the values in the data structure.

Common `IntoIterator` types are data structures and ranges such as `0..10`. Thus, when you say `for
i in 0..10`, you are creating a `Range` that knows its start and end, then calling `into_iter` on it
to convert it to an iterator, and then repeatedly calling `next()` on that iterator to get the
numbers 0, 1, ..., 9.

### Iterators are Lazy

Difference between adapter and consumer - another iterator or consuming?
An important and very useful fact about iterators is that they are _lazy_. This means that, in
general, iterators don't look at the things they're iterating over until they are about to return
it. This is very useful when your iterator will return a large (or perhaps even infinite!) sequence.
Another consequence of this is that you must be careful when using the `map` function with a closure
that mutates things. The closure will not be executed on any elements until the resulting iterator
is consumed.

Iterator adapters are iterators that are based off of other iterators. A simple iterator adapter in
the standard library is the [take](https://doc.rust-lang.org/std/iter/struct.Take.html) adapter.
This iterator contains a counter initialized with a user-defined number and another
iterator. When its `next()` method is called, it decrements the count, and if it's 0, it returns
`None`. Otherwise, it returns `next()` of its contained iterator. In this way, we _adapt_ the inner
iterator to only return the first `n` elements rather than everything.

### Implementing the Iterator trait

Expand All @@ -36,4 +162,3 @@ Most complicated chain of iterator functions that compile down to the same ASM a
### Representation: Closures are a Struct

Closures don't have any further performance penalty over regular fn calls