As Input Parameters

RustRustBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

In this lab, we learn that when writing functions in Rust that take a closure as an input parameter, the closure's complete type must be annotated using one of the traits: Fn, FnMut, or FnOnce, which determine how the closure uses the captured value, either by reference, mutable reference, or value. The compiler captures variables in the least restrictive manner possible based on the chosen trait for the closure.

Note: If the lab does not specify a file name, you can use any file name you want. For example, you can use main.rs, compile and run it with rustc main.rs && ./main.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("`Rust`")) -.-> rust/BasicConceptsGroup(["`Basic Concepts`"]) rust(("`Rust`")) -.-> rust/DataTypesGroup(["`Data Types`"]) rust(("`Rust`")) -.-> rust/FunctionsandClosuresGroup(["`Functions and Closures`"]) rust(("`Rust`")) -.-> rust/DataStructuresandEnumsGroup(["`Data Structures and Enums`"]) rust(("`Rust`")) -.-> rust/AdvancedTopicsGroup(["`Advanced Topics`"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("`Variable Declarations`") rust/BasicConceptsGroup -.-> rust/mutable_variables("`Mutable Variables`") rust/DataTypesGroup -.-> rust/integer_types("`Integer Types`") rust/DataTypesGroup -.-> rust/string_type("`String Type`") rust/DataTypesGroup -.-> rust/type_casting("`Type Conversion and Casting`") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("`Function Syntax`") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("`Expressions and Statements`") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("`Method Syntax`") rust/AdvancedTopicsGroup -.-> rust/traits("`Traits`") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("`Traits for Operator Overloading`") subgraph Lab Skills rust/variable_declarations -.-> lab-99324{{"`As Input Parameters`"}} rust/mutable_variables -.-> lab-99324{{"`As Input Parameters`"}} rust/integer_types -.-> lab-99324{{"`As Input Parameters`"}} rust/string_type -.-> lab-99324{{"`As Input Parameters`"}} rust/type_casting -.-> lab-99324{{"`As Input Parameters`"}} rust/function_syntax -.-> lab-99324{{"`As Input Parameters`"}} rust/expressions_statements -.-> lab-99324{{"`As Input Parameters`"}} rust/method_syntax -.-> lab-99324{{"`As Input Parameters`"}} rust/traits -.-> lab-99324{{"`As Input Parameters`"}} rust/operator_overloading -.-> lab-99324{{"`As Input Parameters`"}} end

As input parameters

While Rust chooses how to capture variables on the fly mostly without type annotation, this ambiguity is not allowed when writing functions. When taking a closure as an input parameter, the closure's complete type must be annotated using one of a few traits, and they're determined by what the closure does with captured value. In order of decreasing restriction, they are:

  • Fn: the closure uses the captured value by reference (&T)
  • FnMut: the closure uses the captured value by mutable reference (&mut T)
  • FnOnce: the closure uses the captured value by value (T)

On a variable-by-variable basis, the compiler will capture variables in the least restrictive manner possible.

For instance, consider a parameter annotated as FnOnce. This specifies that the closure may capture by &T, &mut T, or T, but the compiler will ultimately choose based on how the captured variables are used in the closure.

This is because if a move is possible, then any type of borrow should also be possible. Note that the reverse is not true. If the parameter is annotated as Fn, then capturing variables by &mut T or T are not allowed. However, &T is allowed.

In the following example, try swapping the usage of Fn, FnMut, and FnOnce to see what happens:

// A function which takes a closure as an argument and calls it.
// <F> denotes that F is a "Generic type parameter"
fn apply<F>(f: F) where
    // The closure takes no input and returns nothing.
    F: FnOnce() {
    // ^ TODO: Try changing this to `Fn` or `FnMut`.

    f();
}

// A function which takes a closure and returns an `i32`.
fn apply_to_3<F>(f: F) -> i32 where
    // The closure takes an `i32` and returns an `i32`.
    F: Fn(i32) -> i32 {

    f(3)
}

fn main() {
    use std::mem;

    let greeting = "hello";
    // A non-copy type.
    // `to_owned` creates owned data from borrowed one
    let mut farewell = "goodbye".to_owned();

    // Capture 2 variables: `greeting` by reference and
    // `farewell` by value.
    let diary = || {
        // `greeting` is by reference: requires `Fn`.
        println!("I said {}.", greeting);

        // Mutation forces `farewell` to be captured by
        // mutable reference. Now requires `FnMut`.
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");

        // Manually calling drop forces `farewell` to
        // be captured by value. Now requires `FnOnce`.
        mem::drop(farewell);
    };

    // Call the function which applies the closure.
    apply(diary);

    // `double` satisfies `apply_to_3`'s trait bound
    let double = |x| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}

Summary

Congratulations! You have completed the As Input Parameters lab. You can practice more labs in LabEx to improve your skills.

Other Rust Tutorials you may like