19.3.1 Function Pointers
We have already talked about passing closures into functions. In fact, we can also pass functions into functions.
When they are passed around, functions are coerced into the fn type, which is a function pointer.
For example:
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}");
}
The first parameter of do_twice, f, is of type fn, which means it is a function pointer. It expects a function whose parameter type is i32 and whose return type is also i32. The function body calls f twice.
Output:
The answer is: 12
The Difference Between Function Pointers and Closures
At minimum, closures implement one of the Fn, FnOnce, and FnMut traits. A function pointer fn is a type, not a trait. We can specify fn directly as a parameter type without declaring a generic parameter bounded by the Fn traits.
Function pointers implement all three closure traits, namely Fn, FnOnce, and FnMut. So you can always pass a function pointer as an argument to a function that accepts a closure. That is why we usually prefer generic parameters with closure traits when writing functions, because then the function can accept both closures and ordinary functions.
In some cases, we may want to accept an fn type instead of a closure, for example when interacting with code that does not support closures, such as C functions. How do we write that?
Here is an example:
fn main() {
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(|i| i.to_string())
.collect();
// Splitting the lines is just for readability; it is not required
}
The elements in list_of_numbers are i32, and we want to convert them into String values for list_of_strings. The steps are:
- First, use
iterto produce an iterator - Then use the closure inside
map,|i| i.to_string(), to convert each element - Finally, use
collectto gather all the converted elements into a collection
This code can also be written like this:
fn main() {
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(ToString::to_string)
.collect();
}
The difference is .map(ToString::to_string), where we pass the to_string function directly. The effect is the same as the previous version. By the way, ToString::to_string uses the fully qualified syntax discussed in 19.2. Advanced Traits.
Let’s look at the definition of map:
fn map<B, F>(self, f: F) -> Map<Self, F>
where
Self: Sized,
F: FnMut(Self::Item) -> B
map requires f to implement the FnMut trait, and both closures and function pointers satisfy that requirement, so either can be passed in.
Here is another example:
fn main() {
enum Status {
Value(u32),
Stop,
}
let list_of_statuses: Vec<Status> = (0u32..20)
.map(Status::Value)
.collect();
}
Pay attention to the argument to map. We use the constructor Status::Value to call map on each u32 in the range and create Status::Value instances.
Someone might ask: isn’t Status::Value an enum variant? How did it become a function? That is because in Rust, constructors like this are implemented as functions that take one argument and return a new instance. In other words:
let v = Status::Value(3);
This is just an example. Here, v is initialized, and Status::Value(3) can be viewed as a constructor call: 3 is the constructor’s argument. Since constructors are implemented as functions, we can treat them as functions, with 3 as their argument.
So we can also use such constructors as function pointers that implement closure traits.
19.3.2 Returning Closures
Closures are expressed through traits, so you cannot directly return a closure from a function. Instead, you can return a concrete type that implements the trait.
For example:
fn returns_closure() -> dyn Fn(i32) -> i32 {
|x| x + 1
}
This function tries to return a closure directly.
Output:
error[E0746]: return type cannot be a trait object without pointer indirection
--> src/lib.rs:1:25
|
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
| ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
help: consider returning an `impl Trait` instead of a `dyn Trait`
|
1 - fn returns_closure() -> dyn Fn(i32) -> i32 {
1 + fn returns_closure() -> impl Fn(i32) -> i32 {
|
help: alternatively, box the return type, and wrap all of the returned values in `Box::new`
|
1 ~ fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
2 ~ Box::new(|x| x + 1)
|
For more information about this error, try `rustc --explain E0746`.
error: could not compile `functions-example` (lib) due to 1 previous error
Rust does not know how much space it needs to store the closure, so it reports an error.
Remember where we ran into the same “Rust does not know how much space to allocate” error before? Yes—when learning about linked lists. The solution there was to wrap the list in Box<T>, and we can do the same here:
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
Because the return value is behind a pointer, the return type now has a size known at compile time.
Top comments (0)