From e3d3b3ab195a5ca6cbaaa74c357f80e374f52c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Przytu=C5=82a?= Date: Wed, 30 Oct 2024 09:15:02 +0100 Subject: [PATCH] 06: closures scenario & code --- Cargo.toml | 11 +++ .../closures_capturing.rs | 87 +++++++++++++++++++ .../06_closures_iterators/closures_fun.rs | 63 ++++++++++++++ .../06_closures_iterators/closures_syntax.rs | 20 +++++ .../lessons/06_closures_iterators/index.md | 42 ++++++++- 5 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 content/lessons/06_closures_iterators/closures_capturing.rs create mode 100644 content/lessons/06_closures_iterators/closures_fun.rs create mode 100644 content/lessons/06_closures_iterators/closures_syntax.rs diff --git a/Cargo.toml b/Cargo.toml index 1a5606b..465cca1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,17 @@ path = "content/lessons/05_types_reasoning/generics_fun.rs" [[bin]] name = "05_static_dynamic_dispatch" path = "content/lessons/05_types_reasoning/static_dynamic_dispatch.rs" + +[[bin]] +name = "06_closures_syntax" +path = "content/lessons/06_closures_iterators/closures_syntax.rs" +[[bin]] +name = "06_closures_capturing" +path = "content/lessons/06_closures_iterators/closures_capturing.rs" +[[bin]] +name = "06_closures_fun" +path = "content/lessons/06_closures_iterators/closures_fun.rs" + [[bin]] name = "07_box" path = "content/lessons/07_smart_pointers/box.rs" diff --git a/content/lessons/06_closures_iterators/closures_capturing.rs b/content/lessons/06_closures_iterators/closures_capturing.rs new file mode 100644 index 0000000..c42a640 --- /dev/null +++ b/content/lessons/06_closures_iterators/closures_capturing.rs @@ -0,0 +1,87 @@ +fn main() { + borrowing_immutably_closure(); + borrowing_mutably_closure(); + moving_in_nonmutating_closure(); + moving_in_mutating_closure(); + moving_in_moving_out_closure(); +} + +fn borrowing_immutably_closure() { + let list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + let only_borrows = || println!("From closure: {:?}", list); + + // This would not really only borrow... (it needs Vec by value). + // let only_borrows = || std::mem::drop::>(list); + + println!("Before calling closure: {:?}", list); + only_borrows(); + println!("After calling closure: {:?}", list); +} + +fn borrowing_mutably_closure() { + let mut list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + let mut borrows_mutably = || list.push(7); + + // println!("Before calling closure: {:?}", list); + borrows_mutably(); + println!("After calling closure: {:?}", list); +} + +fn moving_in_nonmutating_closure() { + let list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + // This closure would just borrow the list, because it only prints it. + // However, as spawning threads require passing `impl FnOnce + 'static`, + // we need to use `move` keyword to force the closure to move `list` + // into its captured environment. + std::thread::spawn(move || println!("From thread: {:?}", list)) + .join() + .unwrap(); +} + +fn moving_in_mutating_closure() { + fn append_42(mut appender: impl FnMut(i32)) { + appender(42); + } + + let mut appender = { + let mut list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + // The `move` keyword is necessary to prevent dangling reference to `list`. + // Of course, the borrow checker protects us from compiling code without `move`. + move |num| list.push(num) + }; + + append_42(&mut appender); + append_42(&mut appender); +} + +fn moving_in_moving_out_closure() { + fn append_multiple_times(appender: impl FnOnce(&mut Vec) + Clone) { + let mut list = Vec::new(); + + // We can clone this `FnOnce`, because we additionally require `Clone`. + // If we didn't clone it, we couldn't call it more than *once*. + appender.clone()(&mut list); + appender(&mut list); + } + + let appender = { + let string = String::from("Ala"); + println!("Before defining closure: {:?}", string); + + // The `move` keyword is necessary to prevent dangling reference to `list`. + // Of course, the borrow checker protects us from compiling code without `move`. + move |list: &mut Vec| list.push(string) + }; + + // As `appender` is only `FnOnce`, we need to clone before we consume it by calling it. + append_multiple_times(appender.clone()); + append_multiple_times(appender); +} diff --git a/content/lessons/06_closures_iterators/closures_fun.rs b/content/lessons/06_closures_iterators/closures_fun.rs new file mode 100644 index 0000000..46a8748 --- /dev/null +++ b/content/lessons/06_closures_iterators/closures_fun.rs @@ -0,0 +1,63 @@ +fn main() { + fn some_function() -> String { + String::new() + } + + let v1 = String::from("v1"); + let mut borrowing_immutably_closure = || v1.clone(); + + let mut v2 = String::from("v2"); + let mut borrowing_mutably_closure = || { + v2.push('.'); + v2.clone() + }; + + let v3 = String::from("v3"); + let mut moving_in_nonmutating_closure = move || v3.clone(); + + let mut v4 = String::from("v4"); + let mut moving_in_mutating_closure = move || { + v4.push('.'); + v4.clone() + }; + let v5 = String::from("v5"); + let moving_in_moving_out_closure = || v5; + + let fn_once_callables: [&dyn FnOnce() -> String; 5] = [ + &some_function, + &borrowing_immutably_closure, + &borrowing_mutably_closure, + &moving_in_nonmutating_closure, + &moving_in_moving_out_closure, + ]; + + #[allow(unused_variables)] + for fn_once_callable in fn_once_callables { + // Cannot move a value of type `dyn FnOnce() -> String`. + // The size of `dyn FnOnce() -> String` cannot be statically determined. + // println!("{}", fn_once_callable()); + + // So, for FnOnce, we need to be their owners to be able to call them, + // and we can't have a `dyn` object owned on stack. + // We will solve this problem soon with smart pointers (e.g., Box). + } + + // Mutable reference to FnMut is required to be able to call it. + let fn_mut_callables: [&mut dyn FnMut() -> String; 4] = [ + &mut borrowing_immutably_closure, + &mut borrowing_mutably_closure, + &mut moving_in_nonmutating_closure, + &mut moving_in_mutating_closure, + ]; + + for fn_mut_callable in fn_mut_callables { + println!("{}", fn_mut_callable()); + } + + let fn_callables: &[&dyn Fn() -> String] = + &[&borrowing_immutably_closure, &moving_in_nonmutating_closure]; + + for fn_callable in fn_callables { + println!("{}", fn_callable()); + } +} diff --git a/content/lessons/06_closures_iterators/closures_syntax.rs b/content/lessons/06_closures_iterators/closures_syntax.rs new file mode 100644 index 0000000..1d59e0f --- /dev/null +++ b/content/lessons/06_closures_iterators/closures_syntax.rs @@ -0,0 +1,20 @@ +fn main() { + #[rustfmt::skip] + { + // This is formatted so that with rust-analyzer it renders as well-aligned. + + fn add_one_v1 (x: u32) -> u32 { x + 1 } // This is an ordinary function. + let add_one_v2 = |x: u32| -> u32 { x + 1 }; // Closures use pipes instead of parentheses. + let add_one_v3 = |x| { x + 1 }; // Both parameters and return value can have their types inferred. + let add_one_v4 = |x| x + 1 ; // If the body is a single expression, braces can be omitted. + + let _res = add_one_v1(0_u32); + let _res = add_one_v2(0_u32); + let _res = add_one_v3(0_u32); + let _res = add_one_v4(0_u32); + + // This does not compile, because closures are not generic. + // Their type is inferred once and stays the same. + // let _res = add_one_v4(0_i32); + }; +} diff --git a/content/lessons/06_closures_iterators/index.md b/content/lessons/06_closures_iterators/index.md index 52880c8..f7c96ef 100644 --- a/content/lessons/06_closures_iterators/index.md +++ b/content/lessons/06_closures_iterators/index.md @@ -1,6 +1,6 @@ +++ title = "Closures and Iterators" -date = 2022-11-14 +date = 2024-10-30 weight = 1 [extra] lesson_date = 2024-10-31 @@ -10,7 +10,47 @@ lesson_date = 2024-10-31 Closures (Polish: "domknięcia") are anonymous functions that can access variables from the scope in which they were defined. +## Closure syntax + +{{ include_code_sample(path="lessons/06_closures_iterators/closures_syntax.rs", language="rust") }} + +## Closures' types + +Closures are unnameable types. That is, each closure gets its own unique type from the compiler, +but we cannot use it. Therefore, closures' types must be inferred. +We will often use `impl` keyword with closure traits (e.g., `impl Fn`) - those traits are described below. + +## Closures capture environment + +Closures can capture variables from the environment where they are defined. They can do that in two ways: +- Capturing References (borrowing), or +- Moving Ownership. + +**HOW** closures capture variables is one thing. +But even more important is **WHAT** closures do with their captures. + +{{ include_code_sample(path="lessons/06_closures_iterators/closures_capturing.rs", language="rust") }} + +### Functions & closures hierarchy + +Based on **WHAT** a closure does with its captures, it implements closure traits: + +- `FnOnce` - closures that may move out of their captures environment (and thus called once). +- `FnMut` - closures that may mutate their captures, but don't move out of their captures environment (so can be called multiple times, but require a mutable reference); +- `Fn` - closures that do not mutate their captures (so can be called multiple times through an immutable reference). + +For completeness, there is a (concrete) type of function pointers: +- `fn` - functions, closures with no captures. + +Those traits and the `fn` type form a hierarchy: `fn` < `Fn` < `FnMut` < `FnOnce` + +$$ fn \subseteq Fn \subseteq FnMut \subseteq FnOnce $$ + +## Examples + We'll go through the examples from [Rust by Example](https://doc.rust-lang.org/rust-by-example/fn/closures.html). +More examples will be seen when working with iterators. + # Iterators