Rust Closures with Generic Constraints

RustRustBeginner
Practice Now

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

Introduction

In this lab, it is explained that closures in Rust use type anonymity, which requires the usage of generics when using closures as function parameters.

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/FunctionsandClosuresGroup(["`Functions and Closures`"]) rust(("`Rust`")) -.-> rust/AdvancedTopicsGroup(["`Advanced Topics`"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("`Variable Declarations`") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("`Function Syntax`") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("`Expressions and Statements`") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("`Traits for Operator Overloading`") subgraph Lab Skills rust/variable_declarations -.-> lab-99325{{"`Rust Closures with Generic Constraints`"}} rust/function_syntax -.-> lab-99325{{"`Rust Closures with Generic Constraints`"}} rust/expressions_statements -.-> lab-99325{{"`Rust Closures with Generic Constraints`"}} rust/operator_overloading -.-> lab-99325{{"`Rust Closures with Generic Constraints`"}} end

Type anonymity

Closures succinctly capture variables from enclosing scopes. Does this have any consequences? It surely does. Observe how using a closure as a function parameter requires [generics], which is necessary because of how they are defined:

// `F` must be generic.
fn apply<F>(f: F) where
    F: FnOnce() {
    f();
}

When a closure is defined, the compiler implicitly creates a new anonymous structure to store the captured variables inside, meanwhile implementing the functionality via one of the traits: Fn, FnMut, or FnOnce for this unknown type. This type is assigned to the variable which is stored until calling.

Since this new type is of unknown type, any usage in a function will require generics. However, an unbounded type parameter <T> would still be ambiguous and not be allowed. Thus, bounding by one of the traits: Fn, FnMut, or FnOnce (which it implements) is sufficient to specify its type.

// `F` must implement `Fn` for a closure which takes no
// inputs and returns nothing - exactly what is required
// for `print`.
fn apply<F>(f: F) where
    F: Fn() {
    f();
}

fn main() {
    let x = 7;

    // Capture `x` into an anonymous type and implement
    // `Fn` for it. Store it in `print`.
    let print = || println!("{}", x);

    apply(print);
}

Summary

Congratulations! You have completed the Type Anonymity lab. You can practice more labs in LabEx to improve your skills.

Other Rust Tutorials you may like