Advanced Functions and Closures
Pass functions to the functions
-
Just like you can pass closures to the functions, you can also pass "funtions to the functions".
-
There's a type represented by
fn
, (don't confuse it withFn
, that is a closure trait). -
This
fn
type is called Function Pointer. -
Here's how you can use it:
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!("Answer: {}", answer); }
-
Function pointers implement all three of the closure traits (
Fn
,FnMut
, andFnOnce
), so you can always pass a function pointer as an argument for a function that expects a closure. -
So, instead of passing a closure, you can simply enter a function name, and it will work.
#![allow(unused)] fn main() { // You can either do this let list_of_numbers = vec![1, 2, 3]; let list_of_strings: Vec<String> = list_of_numbers .iter() .map(|i| i.to_string()) // Provide the closure .collect(); // Or you can simply enter the function name let list_of_numbers = vec![1, 2, 3]; let list_of_strings: Vec<String> = list_of_numbers .iter() .map(ToString::to_string) // Enter the function name, wohoo! .collect(); }
-
You can use enum variants as an initializer function. Also, we now know we can pass a function inside a function, so here's what we can also do:
#![allow(unused)] fn main() { enum Status { Value(u32), // So, this works as an initializer function too Stop, } let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect(); // This will create Status instances of the variant Value }
-
You can do the same thing by using closures, it's more of a personal preference. You can use whichever feels more clearer to you.
Return Closures
-
First of all, you can't return functions, that's not allowed in rust.
-
Technically, you are not alllowed to use
fn
(Funciion Pointer) as a return type, but you can return closures. -
Closures are represented by traits.
-
To return a type that implements a trait, you can do either of the two:
- Return a concrete type
- Use dynamic dispatch (it'll allow the function to know concrete type at runtime).
-
Closures don't have the concrete type, you can't send them directly, you'll need to use dynamic dispatch.
#![allow(unused)] fn main() { // FAIL: This will also not work fn returns_closure() -> dyn Fn(i32) -> i32 { |x| x + 1 } }
-
Now, Rust needs one more thing, the size of the closure. Remember the
Sized
trait. -
The solution to this problem, is to wrap the return type with some sort of pointer, in case of strings we use references. For example, using
&str
instead ofstr
. -
Here, we'll use
Box<T>
to simply store it on the heap.#![allow(unused)] fn main() { // This is how you can successfully return a closure fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) } }
-
In case you want to understand better how you can use traits with dynamic dispatch, you can check here.