Task

Take the code snippet at the end of this sheet and complete it where indicated with ✅ TODO.

When you’re done, it should print the following output:

found: hay
found: hay
found: hay
found: needle
found: hay
found: hay
top of the haystack: hay
look, I found the needle: ["needle"]
bale size: 5
empty haystack: [(), (), (), (), ()]

Code snippet to be completed

// NOTE: Once you're done, your ouput could look like this:
//
// found: hay
// found: hay
// found: hay
// found: needle
// found: hay
// found: hay
// top of the haystack: hay
// look, I found the needle: ["needle"]
// emoji haystack: ["🌾", "🌾", "🌾", "🌾", "🌾"]

fn main(){
    // RECAP: ANATOMY OF A CLOSURE
    //============================
    //
    let outside = "it's raining!";
    let rummage = | element | { // 👈 input parameters MAY omit type annotation
                                // if the type can be inferred.

        println!("found: {}", element);
        println!("meanwhile, the weather: {}", outside); //  👈 closures can
        // refer to ("capture") the surrounding environment.
        // *By default*, the environment is borrowed (shared or mutable).
        // When given a choice, this is the behaviour picked by the compiler: shared > mutable > owned.
        // For further details, see https://doc.rust-lang.org/reference/types/closure.html#capture-modes

        // 👈  no return statement: just like functions or blocks, closures *can* return values,
        // but this one doesn't - hence it implictly returns the empty tuple `()`.
    };  // 👈  {}s are only needed for multi-line closures

    let haystack = vec!["hay", "hay", "hay", "needle", "hay", "hay"];

    // 👀  Closures can be used as function arguments.
    haystack.iter().for_each(rummage);

    // since we captured our environment in a shared (immutable) fashion, we're free to run this closure again:
    haystack.iter().for_each(rummage);


    // MUTATING CLOSURES
    //==================
    // Closures can mutate the variables they are capturing

    // ✅ TODO: remove all the hay from `haystack` by checking whether `key` is a needle
    // ✅ TODO: as a side effect, count the hay
    let mut haystack_clone = haystack.clone();
    let mut hay_count = 0;
    haystack_clone.retain(|key| /* check key and increment hay count here */ );
    println!("look, I found the amid between {} pieces of hay: {:?}", hay_count, haystack_clone);

    // 👀  a common use case for closures is to transform collections
    //     using e.g. `map()` and `filter()`.

    // ✅ TODO: use `map()` to convert every "hay" in the haystack to a "🌾"
    let emoji_haystack: Vec<_> = haystack
        .into_iter()
        .filter(|element | *element == "hay")
        .map( /* increment bale size here */ )
        .collect();

    println!("emoji haystack: {:?}", emoji_haystack);

    // ✅ TODO: try uncommenting  the next line. What happens when you re-compile and why?
    // println!("haystack: {:?}", haystack );

    // ✅  Bonus Task: re-implement the creation of `emoji_haystack` using `filter_map()`
    //     https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map
}

Note for Instructors

Distribute the code snippet below in a playground

You can find an example solution in teaching-material/assignments/solutions/fill_in_the_blanks. It is called closures.rs. You can run it by calling cargo run --bin closures in the fill_in_the_blanks directory.