Skip to content

Commit 84ebeea

Browse files
committed
06: closures scenario & code
1 parent 72b7ba1 commit 84ebeea

File tree

5 files changed

+222
-1
lines changed

5 files changed

+222
-1
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ path = "content/lessons/05_types_reasoning/generics_fun.rs"
7878
[[bin]]
7979
name = "05_static_dynamic_dispatch"
8080
path = "content/lessons/05_types_reasoning/static_dynamic_dispatch.rs"
81+
82+
[[bin]]
83+
name = "06_closures_syntax"
84+
path = "content/lessons/06_closures_iterators/closures_syntax.rs"
85+
[[bin]]
86+
name = "06_closures_capturing"
87+
path = "content/lessons/06_closures_iterators/closures_capturing.rs"
88+
[[bin]]
89+
name = "06_closures_fun"
90+
path = "content/lessons/06_closures_iterators/closures_fun.rs"
91+
8192
[[bin]]
8293
name = "07_box"
8394
path = "content/lessons/07_smart_pointers/box.rs"
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
fn main() {
2+
borrowing_immutably_closure();
3+
borrowing_mutably_closure();
4+
moving_in_nonmutating_closure();
5+
moving_in_mutating_closure();
6+
moving_in_moving_out_closure();
7+
}
8+
9+
fn borrowing_immutably_closure() {
10+
let list = vec![1, 2, 3];
11+
println!("Before defining closure: {:?}", list);
12+
13+
let only_borrows = || println!("From closure: {:?}", list);
14+
15+
// This would not really only borrow... (it needs Vec by value).
16+
// let only_borrows = || std::mem::drop::<Vec<_>>(list);
17+
18+
println!("Before calling closure: {:?}", list);
19+
only_borrows();
20+
println!("After calling closure: {:?}", list);
21+
}
22+
23+
fn borrowing_mutably_closure() {
24+
let mut list = vec![1, 2, 3];
25+
println!("Before defining closure: {:?}", list);
26+
27+
let mut borrows_mutably = || list.push(7);
28+
29+
// println!("Before calling closure: {:?}", list);
30+
borrows_mutably();
31+
println!("After calling closure: {:?}", list);
32+
}
33+
34+
fn moving_in_nonmutating_closure() {
35+
let list = vec![1, 2, 3];
36+
println!("Before defining closure: {:?}", list);
37+
38+
// This closure would just borrow the list, because it only prints it.
39+
// However, as spawning threads require passing `impl FnOnce + 'static`,
40+
// we need to use `move` keyword to force the closure to move `list`
41+
// into its captured environment.
42+
std::thread::spawn(move || println!("From thread: {:?}", list))
43+
.join()
44+
.unwrap();
45+
}
46+
47+
fn moving_in_mutating_closure() {
48+
fn append_42(mut appender: impl FnMut(i32)) {
49+
appender(42);
50+
}
51+
52+
let mut appender = {
53+
let mut list = vec![1, 2, 3];
54+
println!("Before defining closure: {:?}", list);
55+
56+
// The `move` keyword is necessary to prevent dangling reference to `list`.
57+
// Of course, the borrow checker protects us from compiling code without `move`.
58+
move |num| list.push(num)
59+
};
60+
61+
append_42(&mut appender);
62+
append_42(&mut appender);
63+
}
64+
65+
fn moving_in_moving_out_closure() {
66+
fn append_multiple_times(appender: impl FnOnce(&mut Vec<String>) + Clone) {
67+
let mut list = Vec::new();
68+
69+
// We can clone this `FnOnce`, because we additionally require `Clone`.
70+
// If we didn't clone it, we couldn't call it more than *once*.
71+
appender.clone()(&mut list);
72+
appender(&mut list);
73+
}
74+
75+
let appender = {
76+
let string = String::from("Ala");
77+
println!("Before defining closure: {:?}", string);
78+
79+
// The `move` keyword is necessary to prevent dangling reference to `list`.
80+
// Of course, the borrow checker protects us from compiling code without `move`.
81+
move |list: &mut Vec<String>| list.push(string)
82+
};
83+
84+
// As `appender` is only `FnOnce`, we need to clone before we consume it by calling it.
85+
append_multiple_times(appender.clone());
86+
append_multiple_times(appender);
87+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
fn main() {
2+
fn some_function() -> String {
3+
String::new()
4+
}
5+
6+
let v1 = String::from("v1");
7+
let mut borrowing_immutably_closure = || v1.clone();
8+
9+
let mut v2 = String::from("v2");
10+
let mut borrowing_mutably_closure = || {
11+
v2.push('.');
12+
v2.clone()
13+
};
14+
15+
let v3 = String::from("v3");
16+
let mut moving_in_nonmutating_closure = move || v3.clone();
17+
18+
let mut v4 = String::from("v4");
19+
let mut moving_in_mutating_closure = move || {
20+
v4.push('.');
21+
v4.clone()
22+
};
23+
let v5 = String::from("v5");
24+
let moving_in_moving_out_closure = || v5;
25+
26+
let fn_once_callables: [&dyn FnOnce() -> String; 5] = [
27+
&some_function,
28+
&borrowing_immutably_closure,
29+
&borrowing_mutably_closure,
30+
&moving_in_nonmutating_closure,
31+
&moving_in_moving_out_closure,
32+
];
33+
34+
#[allow(unused_variables)]
35+
for fn_once_callable in fn_once_callables {
36+
// Cannot move a value of type `dyn FnOnce() -> String`.
37+
// The size of `dyn FnOnce() -> String` cannot be statically determined.
38+
// println!("{}", fn_once_callable());
39+
40+
// So, for FnOnce, we need to be their owners to be able to call them,
41+
// and we can't have a `dyn` object owned on stack.
42+
// We will solve this problem soon with smart pointers (e.g., Box).
43+
}
44+
45+
// Mutable reference to FnMut is required to be able to call it.
46+
let fn_mut_callables: [&mut dyn FnMut() -> String; 4] = [
47+
&mut borrowing_immutably_closure,
48+
&mut borrowing_mutably_closure,
49+
&mut moving_in_nonmutating_closure,
50+
&mut moving_in_mutating_closure,
51+
];
52+
53+
for fn_mut_callable in fn_mut_callables {
54+
println!("{}", fn_mut_callable());
55+
}
56+
57+
let fn_callables: &[&dyn Fn() -> String] =
58+
&[&borrowing_immutably_closure, &moving_in_nonmutating_closure];
59+
60+
for fn_callable in fn_callables {
61+
println!("{}", fn_callable());
62+
}
63+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
fn main() {
2+
#[rustfmt::skip]
3+
{
4+
// This is formatted so that with rust-analyzer it renders as well-aligned.
5+
6+
fn add_one_v1 (x: u32) -> u32 { x + 1 } // This is an ordinary function.
7+
let add_one_v2 = |x: u32| -> u32 { x + 1 }; // Closures use pipes instead of parentheses.
8+
let add_one_v3 = |x| { x + 1 }; // Both parameters and return value can have their types inferred.
9+
let add_one_v4 = |x| x + 1 ; // If the body is a single expression, braces can be omitted.
10+
11+
let _res = add_one_v1(0_u32);
12+
let _res = add_one_v2(0_u32);
13+
let _res = add_one_v3(0_u32);
14+
let _res = add_one_v4(0_u32);
15+
16+
// This does not compile, because closures are not generic.
17+
// Their type is inferred once and stays the same.
18+
// let _res = add_one_v4(0_i32);
19+
};
20+
}

content/lessons/06_closures_iterators/index.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
+++
22
title = "Closures and Iterators"
3-
date = 2022-11-14
3+
date = 2024-10-30
44
weight = 1
55
[extra]
66
lesson_date = 2024-10-31
@@ -10,7 +10,47 @@ lesson_date = 2024-10-31
1010

1111
Closures (Polish: "domknięcia") are anonymous functions that can access variables from the scope in which they were defined.
1212

13+
## Closure syntax
14+
15+
{{ include_code_sample(path="lessons/06_closures_iterators/closures_syntax.rs", language="rust") }}
16+
17+
## Closures' types
18+
19+
Closures are unnameable types. That is, each closure gets its own unique type from the compiler,
20+
but we cannot use it. Therefore, closures' types must be inferred.
21+
We will often use `impl` keyword with closure traits (e.g., `impl Fn`) - those traits are described below.
22+
23+
## Closures capture environment
24+
25+
Closures can capture variables from the environment where they are defined. They can do that in two ways:
26+
- Capturing References (borrowing), or
27+
- Moving Ownership.
28+
29+
**HOW** closures capture variables is one thing.
30+
But even more important is **WHAT** closures do with their captures.
31+
32+
{{ include_code_sample(path="lessons/06_closures_iterators/closures_capturing.rs", language="rust") }}
33+
34+
### Functions & closures hierarchy
35+
36+
Based on **WHAT** a closure does with its captures, it implements closure traits:
37+
38+
- `FnOnce` - closures that may move out of their captures environment (and thus called once).
39+
- `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);
40+
- `Fn` - closures that do not mutate their captures (so can be called multiple times through an immutable reference).
41+
42+
For completeness, there is a (concrete) type of function pointers:
43+
- `fn` - functions, closures with no captures.
44+
45+
Those traits and the `fn` type form a hierarchy: `fn` < `Fn` < `FnMut` < `FnOnce`
46+
47+
$$ fn \subseteq Fn \subseteq FnMut \subseteq FnOnce $$
48+
49+
## Examples
50+
1351
We'll go through the examples from [Rust by Example](https://doc.rust-lang.org/rust-by-example/fn/closures.html).
52+
More examples will be seen when working with iterators.
53+
1454

1555
# Iterators
1656

0 commit comments

Comments
 (0)