mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
145 lines
5.7 KiB
Markdown
145 lines
5.7 KiB
Markdown
# Advanced Functions and Closures
|
|
This section includes some more advanced features including function pointers and returning closures.
|
|
|
|
## Function Pointers
|
|
We have talked about how to pass closures to functions; you can also pass regular functions to functions.
|
|
|
|
This technique is useful when you want to pass a function that you have already defined rather than defining a new closure.
|
|
|
|
Functions coerce to the type `fn` (with a lowercase f), not to be confused with, not to be confused with the `Fn` closure trait.
|
|
|
|
The `fn` type is called a *function pointer*.
|
|
|
|
Passing function pointers will allow you to use functions as arguments to other functions.
|
|
|
|
The syntax for specifying that a parameter is a function pointer is similar to that of closures as shown below.
|
|
|
|
This shows that we have defined a function `add_one` that adds one to its parameter.
|
|
|
|
The function `do_twice` takes two parameters: a function pointer to any function takes an `i32` parameter and returns an `i32`, and one `i32` value.
|
|
|
|
The `do_twice` function calls the function `f` twice, passing it the `arg` value, then adds the two function call results together.
|
|
|
|
The `main` function calls `do_twice` with the arguments `add_one` and `5`.
|
|
```rust
|
|
fn add_one(x: i32) -> i32 {
|
|
x + 1
|
|
}
|
|
|
|
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
|
|
f(arg) + f(arg)
|
|
}
|
|
|
|
fn main() {
|
|
let answer = do_twice(add_one, 5);
|
|
|
|
println!("The answer is: {answer}");
|
|
}
|
|
```
|
|
Output
|
|
```
|
|
The answer is: 12
|
|
```
|
|
Here we specify that the parameter `f` in `do_twice` is an `fn` that takes one parameter of type `i32` and returns a `i32`.
|
|
|
|
We can then call `f` from the body of `do_twice`.
|
|
|
|
In `main` we can pass the function name `add_one` as the first arg to `do_twice`.
|
|
|
|
Unlike closures, `fn` is a type rather than a trait, so we need to specify `fn` as the parameter type directly rather than declaring a generic type parameter with one of the `Fn` traits as a trait bound.
|
|
|
|
Function pointers implement all three of the closure traits (`Fn`, `FnMut` and `FnOnce`).
|
|
|
|
This means that you can always pass a function pointer as an argument for a function that expects a closure.
|
|
|
|
It is best to write functions using a generic type and one of the closure traits so your functions can accept either functions or closures.
|
|
|
|
That being said, one example of where you would only want to accept `fn` and not closures is when interfacing with external code that doesn't have closures.
|
|
|
|
C functions can accept functions as arguments, but C doesn't have closures.
|
|
|
|
As an example of where you could use either a closure defined inline or a named function.
|
|
|
|
Lets look at a use of the `map` method provided by the `Iterator` trait in the std library.
|
|
|
|
To use the `map` function to turn a vector of numbers into a vector of strings, we could use a closure.
|
|
|
|
Like this:
|
|
```rust
|
|
let list_of_numbers = vec![1, 2, 3];
|
|
let list_of_strings: Vec<String> =
|
|
list_of_numbers.iter().map(|i| i.to_string()).collect();
|
|
```
|
|
We could have a function as the argument to `map` instead of the closure.
|
|
|
|
Like this:
|
|
```rust
|
|
let list_of_numbers = vec![1, 2, 3];
|
|
let list_of_strings: Vec<String> =
|
|
list_of_numbers.iter().map(ToString::to_string).collect();
|
|
```
|
|
Note, we must use the fully qualified syntax that we talked about earlier in ["Advanced Traits"](./Advanced%20Traits.md) section.
|
|
|
|
This is because there are multiple functions available named `to_string`.
|
|
|
|
Here we are using the `to_string` function defined in the `ToString` trait, which is in the std library has implemented for any type that implements `Display`.
|
|
|
|
Recall form the ["Enum values"]() section of Ch6 that the name of each enum variant that we define also becomes an initializer function.
|
|
|
|
We can use these initializer functions as function pointers that implement the closure traits, this means we can specify the initializer functions as arguments for methods that take closures.
|
|
|
|
Like this:
|
|
```rust
|
|
enum Status {
|
|
Value(u32),
|
|
Stop,
|
|
}
|
|
|
|
let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
|
|
```
|
|
Here we creates `Status::Value` instances using each `u32` value in the range that `map` is called on by using the initializer function of `Status::Value`.
|
|
|
|
Some prefer to use this style, and some people prefer to use closures.
|
|
|
|
They compile to the same code, so use whatever style is clearer to you.
|
|
|
|
## Returning Closures
|
|
Closures are represented by traits, meaning that you can't return closures directly.
|
|
|
|
In cases where you want to return a trait, you instead use the concrete type that implements the trait as the return value of the function.
|
|
|
|
You are unable to do that with closures because they do not have a concrete type that is returnable.
|
|
|
|
You are not allowed to use the function pointer `fn` as a return type, for example.
|
|
|
|
Instead, you would normally use the `impl Trait` syntax we learned from Ch10.
|
|
|
|
You can return any function type using the `Fn`, `FnOnce` and `FnMut`.
|
|
|
|
This code would work just fine.
|
|
```rust
|
|
fn returns_closure() -> impl Fn(i32) -> i32 {
|
|
|x| x + 1
|
|
}
|
|
```
|
|
As noted in the ["Closure Type Inference and Annotation"](./Closures.md#closure-type-inference-and-annoation) section in Ch13, each closure is also its own distinct type.
|
|
|
|
If you needed to work with multiple functions that have the same signature but slightly different implementations, you will need to use a trait object for them.
|
|
```rust
|
|
fn main() {
|
|
let handlers = vec![returns_closure(), returns_initialized_closure(123)];
|
|
for handler in handlers {
|
|
let output = handler(5);
|
|
println!("{output}");
|
|
}
|
|
}
|
|
|
|
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
|
|
Box::new(|x| x + 1)
|
|
}
|
|
|
|
fn returns_initialized_closure(init: i32) -> Box<dyn Fn(i32) -> i32> {
|
|
Box::new(move |x| x + init)
|
|
}
|
|
```
|
|
This will compile just fine, but it wouldn't if we had tried to stick with `impl Fn(i32) -> i32`. |