Skip to content

Commit 4c80d7b

Browse files
committed
WIP Closures1
1 parent bc3808c commit 4c80d7b

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed

exercises/18_closures/README.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Closures
2+
3+
Closures in Rust are anonymous functions that can capture variables from their surrounding environment. They are similar to lambda expressions or anonymous functions in other languages like Python or JavaScript, but with a few key differences that stem from Rust's ownership system and its focus on safety and performance.
4+
5+
## How Closures Work in Rust
6+
7+
In Rust, closures are defined using the pipe syntax (ie. `|x: String|`) to enclose their parameters and are generally more flexible than functions because they can capture variables from their environment in three different ways:
8+
9+
By Shared Reference (`&T`): Borrowing values from the environment without taking ownership.
10+
By Exclusive Reference (`&mut T`): Mutably borrowing values, allowing them to be modified.
11+
By Value (`T`): Taking ownership of the values, which can be moved into the closure.
12+
This flexibility allows closures to be used in a variety of contexts, such as iterators, where they can efficiently process data streams without the overhead of function calls. Rust's closures can also implement one of the three `Fn`, `FnMut`, or `FnOnce` traits, depending on how they capture their environment, which makes them highly adaptable for various use cases.
13+
14+
## Comparison to Other Languages
15+
16+
Unlike higher-level languages where closures often simply reference variables from their enclosing scope, Rust's closures need to conform to strict ownership and borrowing rules. This ensures memory safety but also introduces complexities not found in more dynamic languages. For example, deciding whether a closure should move or borrow variables can be non-trivial, especially when dealing with mutable or non-`Copy` types.
17+
18+
## Common Challenges
19+
20+
One of the challenges with closures in Rust is understanding how they capture variables and the implications for the borrow checker. For instance, if a closure moves a variable, that variable is no longer accessible after the closure is called, which can lead to borrow checker errors that might confuse newcomers. Additionally, because closures in Rust can sometimes have complex types (especially when capturing environment variables), they often require type annotations or explicit trait bounds when used in generic contexts.
21+
22+
## Further information
23+
24+
- [The Rust Book](https://doc.rust-lang.org/stable/book/ch13-01-closures.html)
25+
- [Rust By Example](https://doc.rust-lang.org/rust-by-example/fn/closures.html)

exercises/18_closures/closures1.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// closures1.rs
2+
//
3+
// "Why do we even need closures?" is a question that gets asked from time to time.
4+
// While it's true that most things that closures can do can also be done with
5+
// regular old structs and enums, closures can make things a lot more clear with a lot
6+
// less clutter compared to structs.
7+
//
8+
// Below is a good example of how one could implement a capturing closure using structs,
9+
// and how closures simplifies this greatly.
10+
//
11+
// Execute `rustlings hint closures1` or use the `hint` watch subcommand for a hint.
12+
13+
// I AM NOT DONE
14+
15+
16+
trait Doorman {
17+
fn greet_customer(&self, customer_name: &str);
18+
}
19+
20+
struct GreeterWithState<'a> {
21+
greeting: &'a str,
22+
}
23+
24+
impl Doorman for GreeterWithState<'_> {
25+
fn greet_customer(&self, customer_name: &str) {
26+
println!("{}, {}?", self.greeting, customer_name);
27+
}
28+
}
29+
30+
fn greet_customers(doorman: impl Doorman) {
31+
doorman.greet_customer("Bill");
32+
doorman.greet_customer("Alex");
33+
doorman.greet_customer("John");
34+
doorman.greet_customer("Jessie");
35+
}
36+
37+
fn greet_customers_closure(doorman: impl Fn(&str)) {
38+
doorman("Bill");
39+
doorman("Alex");
40+
doorman("John");
41+
doorman("Jessie");
42+
}
43+
44+
fn main() {
45+
let greeting = String::from("Hello! How are you");
46+
47+
// Method 1 for passing in functions with state.
48+
// Just create a struct, store the state, and add a method.
49+
// If you need to be generic, it can be a trait method.
50+
let doorman = GreeterWithState {
51+
greeting: &greeting,
52+
};
53+
greet_customers(doorman);
54+
55+
// Method 2 for passing in functions with state.
56+
// Notice that the body of this closure is exactly the same
57+
// as GreeterWithState's Doorman implementation.
58+
//
59+
// This makes things much cleaner with less clutter, but
60+
// we are forgetting something very important.
61+
greet_customers_closure(|customer_name| {
62+
println!("{}, {}?", self.greeting, customer_name); // TODO: Only modify this line
63+
})
64+
}

exercises/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
| traits | §10.2 |
2020
| tests | §11.1 |
2121
| lifetimes | §10.3 |
22+
| closures | §13.1 |
2223
| iterators | §13.2-4 |
2324
| threads | §16.1-3 |
2425
| smart_pointers | §15, §16.3 |

info.toml

+11
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,17 @@ To handle that you need to add a special attribute to the test function.
932932
You can refer to the docs:
933933
https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html#checking-for-panics-with-should_panic"""
934934

935+
# CLOSURES
936+
937+
[[exercises]]
938+
name = "closures1"
939+
path = "exercises/18_closures/closures1.rs"
940+
mode = "compile"
941+
hint = """
942+
Self is a concept that is only used in struct/enum methods.
943+
944+
Closures in Rust do not have a self to refer to, unlike other languages that might use this or self."""
945+
935946
# STANDARD LIBRARY TYPES
936947

937948
[[exercises]]

0 commit comments

Comments
 (0)