mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-26 00:00:03 +03:00
Compare commits
7 commits
cf2134455b
...
0a815fd94b
Author | SHA1 | Date | |
---|---|---|---|
0a815fd94b | |||
e6cb104294 | |||
410eb69d25 | |||
243cf5f261 | |||
8aa8b492ea | |||
d9cfdf7c65 | |||
286e803d40 |
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -1,3 +1,34 @@
|
|||
<a name="6.4.0"></a>
|
||||
|
||||
## 6.4.0 (2024-11-11)
|
||||
|
||||
### Added
|
||||
|
||||
- The list of exercises is now searchable by pressing `s` or `/` 🔍️ (thanks to [@frroossst](https://github.com/frroossst))
|
||||
- New option `c` in the prompt to manually check all exercises ✅ (thanks to [@Nahor](https://github.com/Nahor))
|
||||
- New command `check-all` to manually check all exercises ✅ (thanks to [@Nahor](https://github.com/Nahor))
|
||||
- Addictive animation for showing the progress of checking all exercises. A nice showcase of parallelism in Rust ✨
|
||||
- New option `x` in the prompt to reset the file of the current exercise 🔄
|
||||
- Allow `dead_code` for all exercises and solutions ⚰️ (thanks to [@huss4in](https://github.com/huss4in))
|
||||
- Pause input while running an exercise to avoid unexpected prompt interactions ⏸️
|
||||
- Limit the maximum number of exercises to 999. Any third-party exercises willing to reach that limit? 🔝
|
||||
|
||||
### Changed
|
||||
|
||||
- `enums3`: Remove redundant enum definition task (thanks to [@senekor](https://github.com/senekor))
|
||||
- `if2`: Make the exercise less confusing by avoiding "fizz", "fuzz", "foo", "bar" and "baz" (thanks to [@senekor](https://github.com/senekor))
|
||||
- `hashmap3`: Use the method `Entry::or_default`.
|
||||
- Update the state of all exercises when checking all of them (thanks to [@Nahor](https://github.com/Nahor))
|
||||
- The main prompt doesn't need a confirmation with ENTER on Unix-like systems anymore.
|
||||
- No more jumping back to a previous exercise when its file is changed. Use the list to jump between exercises.
|
||||
- Dump the solution file after an exercise is done even if the solution's directory doesn't exist.
|
||||
- Rework the footer in the list.
|
||||
- Optimize the file watcher.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix bad contrast in the list on terminals with a light theme.
|
||||
|
||||
<a name="6.3.0"></a>
|
||||
|
||||
## 6.3.0 (2024-08-29)
|
||||
|
@ -113,7 +144,7 @@ You can read about the motivations of this change in [this issue](https://github
|
|||
|
||||
### List mode
|
||||
|
||||
A list mode was added using [Ratatui](https://ratatui.rs).
|
||||
A new list mode was added!
|
||||
You can enter it by entering `l` in the watch mode.
|
||||
It offers the following features:
|
||||
|
||||
|
@ -814,7 +845,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
|
|||
|
||||
#### Bug Fixes
|
||||
|
||||
- Update deps to version compatable with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401))
|
||||
- Update deps to version compatible with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401))
|
||||
- **docs:**
|
||||
- Added a necessary step to Windows installation process (#242) ([3906efcd](https://github.com/rust-lang/rustlings/commit/3906efcd52a004047b460ed548037093de3f523f))
|
||||
- Fixed mangled sentence from book; edited for clarity (#266) ([ade52ff](https://github.com/rust-lang/rustlings/commit/ade52ffb739987287ddd5705944c8777705faed9))
|
||||
|
|
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -451,7 +451,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustlings"
|
||||
version = "6.3.0"
|
||||
version = "6.4.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
@ -468,7 +468,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustlings-macros"
|
||||
version = "6.3.0"
|
||||
version = "6.4.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
|
|
|
@ -6,7 +6,7 @@ exclude = [
|
|||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "6.3.0"
|
||||
version = "6.4.0"
|
||||
authors = [
|
||||
"Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it
|
||||
"Liv <mokou@fastmail.com>", # https://github.com/shadows-withal
|
||||
|
@ -51,7 +51,7 @@ clap = { version = "4.5.20", features = ["derive"] }
|
|||
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
|
||||
notify = "7.0.0"
|
||||
os_pipe = "1.2.1"
|
||||
rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" }
|
||||
rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" }
|
||||
serde_json = "1.0.132"
|
||||
serde.workspace = true
|
||||
toml_edit.workspace = true
|
||||
|
@ -70,6 +70,7 @@ panic = "abort"
|
|||
|
||||
[package.metadata.release]
|
||||
pre-release-hook = ["./release-hook.sh"]
|
||||
pre-release-commit-message = "Release 🎉"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
|
|
|
@ -140,6 +140,10 @@ bin = [
|
|||
{ name = "tests2_sol", path = "../solutions/17_tests/tests2.rs" },
|
||||
{ name = "tests3", path = "../exercises/17_tests/tests3.rs" },
|
||||
{ name = "tests3_sol", path = "../solutions/17_tests/tests3.rs" },
|
||||
{ name = "closures1", path = "../exercises/18_closures/closures1.rs" },
|
||||
{ name = "closures1_sol", path = "../solutions/18_closures/closures1.rs" },
|
||||
{ name = "closures2", path = "../exercises/18_closures/closures2.rs" },
|
||||
{ name = "closures2_sol", path = "../solutions/18_closures/closures2.rs" },
|
||||
{ name = "iterators1", path = "../exercises/18_iterators/iterators1.rs" },
|
||||
{ name = "iterators1_sol", path = "../solutions/18_iterators/iterators1.rs" },
|
||||
{ name = "iterators2", path = "../exercises/18_iterators/iterators2.rs" },
|
||||
|
|
25
exercises/18_closures/README.md
Normal file
25
exercises/18_closures/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Closures
|
||||
|
||||
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.
|
||||
|
||||
## How Closures Work in Rust
|
||||
|
||||
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:
|
||||
|
||||
By Shared Reference (`&T`): Borrowing values from the environment without taking ownership.
|
||||
By Exclusive Reference (`&mut T`): Mutably borrowing values, allowing them to be modified.
|
||||
By Value (`T`): Taking ownership of the values, which can be moved into the closure.
|
||||
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.
|
||||
|
||||
## Comparison to Other Languages
|
||||
|
||||
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.
|
||||
|
||||
## Common Challenges
|
||||
|
||||
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.
|
||||
|
||||
## Further information
|
||||
|
||||
- [The Rust Book](https://doc.rust-lang.org/stable/book/ch13-01-closures.html)
|
||||
- [Rust By Example](https://doc.rust-lang.org/rust-by-example/fn/closures.html)
|
62
exercises/18_closures/closures1.rs
Normal file
62
exercises/18_closures/closures1.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
// closures1.rs
|
||||
//
|
||||
// "Why do we even need closures?" is a question that gets asked from time to time.
|
||||
// While it's true that most things that closures can do can also be done with
|
||||
// regular old structs and enums, closures can make things a lot more clear with a lot
|
||||
// less clutter compared to structs.
|
||||
//
|
||||
// Below is a good example of how one could implement a capturing closure using structs,
|
||||
// and how closures simplifies this greatly.
|
||||
//
|
||||
// Execute `rustlings hint closures1` or use the `hint` watch subcommand for a hint.
|
||||
|
||||
|
||||
trait Doorman {
|
||||
fn greet_customer(&self, customer_name: &str);
|
||||
}
|
||||
|
||||
struct GreeterWithState<'a> {
|
||||
greeting: &'a str,
|
||||
}
|
||||
|
||||
impl Doorman for GreeterWithState<'_> {
|
||||
fn greet_customer(&self, customer_name: &str) {
|
||||
println!("{}, {}?", self.greeting, customer_name);
|
||||
}
|
||||
}
|
||||
|
||||
fn greet_customers(doorman: impl Doorman) {
|
||||
doorman.greet_customer("Bill");
|
||||
doorman.greet_customer("Alex");
|
||||
doorman.greet_customer("John");
|
||||
doorman.greet_customer("Jessie");
|
||||
}
|
||||
|
||||
fn greet_customers_closure(doorman: impl Fn(&str)) {
|
||||
doorman("Bill");
|
||||
doorman("Alex");
|
||||
doorman("John");
|
||||
doorman("Jessie");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let greeting = String::from("Hello! How are you");
|
||||
|
||||
// Method 1 for passing in functions with state.
|
||||
// Just create a struct, store the state, and add a method.
|
||||
// If you need to be generic, it can be a trait method.
|
||||
let doorman = GreeterWithState {
|
||||
greeting: &greeting,
|
||||
};
|
||||
greet_customers(doorman);
|
||||
|
||||
// Method 2 for passing in functions with state.
|
||||
// Notice that the body of this closure is exactly the same
|
||||
// as GreeterWithState's Doorman implementation.
|
||||
//
|
||||
// This makes things much cleaner with less clutter, but
|
||||
// we are forgetting something very important.
|
||||
greet_customers_closure(|customer_name| {
|
||||
println!("{}, {}?", self.greeting, customer_name); // TODO: Only modify this line
|
||||
})
|
||||
}
|
47
exercises/18_closures/closures2.rs
Normal file
47
exercises/18_closures/closures2.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
// closures2.rs
|
||||
//
|
||||
// How do closures capture their state? Well, the answer is "it depends on how you use it!"
|
||||
//
|
||||
// Usage inside the closure body will tell the compiler how the value should be captured.
|
||||
//
|
||||
// Capture by shared reference? Mutable reference? Ownership? Let's try and see!
|
||||
//
|
||||
// Execute `rustlings hint closures2` or use the `hint` watch subcommand for a hint.
|
||||
|
||||
fn main() {
|
||||
// Using a non-Copy type because it makes reasoning about capturing easier
|
||||
let s = String::from("Hello, rustlings!");
|
||||
let capture_by_ref = || {
|
||||
println!("{s}"); // This only requires a &String, so it only captures a &String
|
||||
};
|
||||
// You can continue to use s as a &String outside the closure, but not &mut String or String.
|
||||
println!("Outside capture_by_ref closure: {s}");
|
||||
capture_by_ref();
|
||||
|
||||
// Notice the mut here
|
||||
// v
|
||||
let mut s = String::from("Hello, rustlings!");
|
||||
let mut capture_by_mut = || {
|
||||
s.truncate(5); // Requires &mut String: also can be written as String::truncate(&mut s, 5);
|
||||
println!("{s}"); // This should print nothing (and a line break)
|
||||
// Since the "most" we need is mutable, it captures a single mutable reference to String.
|
||||
};
|
||||
capture_by_mut();
|
||||
|
||||
let mut s = String::from("Hello, rustlings!");
|
||||
let capture_by_ownership = || {
|
||||
s.truncate(5); // Requires &mut String
|
||||
println!("{s}"); // This should print nothing (and a line break)
|
||||
let boxed = s.into_boxed_str(); // Requires ownership: String::into_boxed_str(s);
|
||||
println!("{boxed}"); // This should print nothing (and a line break)
|
||||
};
|
||||
capture_by_ownership();
|
||||
|
||||
let mut s = String::from("Hello, rustlings!");
|
||||
let mut quiz = || {
|
||||
let captured_s = &mut s; // TODO Fix this compiler error
|
||||
println!("Inside Closure quiz {captured_s}");
|
||||
};
|
||||
println!("Outside Closure quiz {s}");
|
||||
quiz();
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
| traits | §10.2 |
|
||||
| lifetimes | §10.3 |
|
||||
| tests | §11.1 |
|
||||
| closures | §13.1 |
|
||||
| iterators | §13.2-4 |
|
||||
| smart_pointers | §15, §16.3 |
|
||||
| threads | §16.1-3 |
|
||||
|
|
|
@ -877,6 +877,28 @@ To handle that, you need to add a special attribute to the test function.
|
|||
You can refer to the docs:
|
||||
https://doc.rust-lang.org/book/ch11-01-writing-tests.html#checking-for-panics-with-should_panic"""
|
||||
|
||||
# CLOSURES
|
||||
|
||||
[[exercises]]
|
||||
name = "closures1"
|
||||
dir = "18_closures"
|
||||
test = false
|
||||
hint = """
|
||||
Self is a concept that is only used in struct/enum methods.
|
||||
|
||||
Closures in Rust do not have a self to refer to, unlike other languages that might use this or self."""
|
||||
|
||||
[[exercises]]
|
||||
name = "closures2"
|
||||
dir = "18_closures"
|
||||
test = false
|
||||
hint = """
|
||||
Capturing a mutable reference manually will also force the closure to capture s by mutable reference.
|
||||
|
||||
The println macro only requires a shared reference.
|
||||
|
||||
Also make sure that you don't declare s or the closure with mut when it is no longer necessary."""
|
||||
|
||||
# STANDARD LIBRARY TYPES
|
||||
|
||||
[[exercises]]
|
||||
|
|
61
solutions/18_closures/closures1.rs
Normal file
61
solutions/18_closures/closures1.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
// closures1.rs
|
||||
//
|
||||
// "Why do we even need closures?" is a question that gets asked from time to time.
|
||||
// While it's true that most things that closures can do can also be done with
|
||||
// regular old structs and enums, closures can make things a lot more clear with a lot
|
||||
// less clutter compared to structs.
|
||||
//
|
||||
// Below is a good example of how one could implement a capturing closure using structs,
|
||||
// and how closures simplifies this greatly.
|
||||
//
|
||||
// Execute `rustlings hint closures1` or use the `hint` watch subcommand for a hint.
|
||||
|
||||
trait Doorman {
|
||||
fn greet_customer(&self, customer_name: &str);
|
||||
}
|
||||
|
||||
struct GreeterWithState<'a> {
|
||||
greeting: &'a str,
|
||||
}
|
||||
|
||||
impl Doorman for GreeterWithState<'_> {
|
||||
fn greet_customer(&self, customer_name: &str) {
|
||||
println!("{}, {}?", self.greeting, customer_name);
|
||||
}
|
||||
}
|
||||
|
||||
fn greet_customers(doorman: impl Doorman) {
|
||||
doorman.greet_customer("Bill");
|
||||
doorman.greet_customer("Alex");
|
||||
doorman.greet_customer("John");
|
||||
doorman.greet_customer("Jessie");
|
||||
}
|
||||
|
||||
fn greet_customers_closure(doorman: impl Fn(&str)) {
|
||||
doorman("Bill");
|
||||
doorman("Alex");
|
||||
doorman("John");
|
||||
doorman("Jessie");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let greeting = String::from("Hello! How are you");
|
||||
|
||||
// Method 1 for passing in functions with state.
|
||||
// Just create a struct, store the state, and add a method.
|
||||
// If you need to be generic, it can be a trait method.
|
||||
let doorman = GreeterWithState {
|
||||
greeting: &greeting,
|
||||
};
|
||||
greet_customers(doorman);
|
||||
|
||||
// Method 2 for passing in functions with state.
|
||||
// Notice that the body of this closure is exactly the same
|
||||
// as GreeterWithState's Doorman implementation.
|
||||
//
|
||||
// This makes things much cleaner with less clutter, but
|
||||
// we are forgetting something very important.
|
||||
greet_customers_closure(|customer_name| {
|
||||
println!("{}, {}?", greeting, customer_name); // Capture greeting by reference
|
||||
})
|
||||
}
|
47
solutions/18_closures/closures2.rs
Normal file
47
solutions/18_closures/closures2.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
// closures2.rs
|
||||
//
|
||||
// How do closures capture their state? Well, the answer is "it depends on how you use it!"
|
||||
//
|
||||
// Usage inside the closure body will tell the compiler how the value should be captured.
|
||||
//
|
||||
// Capture by shared reference? Mutable reference? Ownership? Let's try and see!
|
||||
//
|
||||
// Execute `rustlings hint closures2` or use the `hint` watch subcommand for a hint.
|
||||
|
||||
fn main() {
|
||||
// Using a non-Copy type because it makes reasoning about capturing easier
|
||||
let s = String::from("Hello, rustlings!");
|
||||
let capture_by_ref = || {
|
||||
println!("{s}"); // This only requires a &String, so it only captures a &String
|
||||
};
|
||||
// You can continue to use s as a &String outside the closure, but not &mut String or String.
|
||||
println!("Outside capture_by_ref closure: {s}");
|
||||
capture_by_ref();
|
||||
|
||||
// Notice the mut here
|
||||
// v
|
||||
let mut s = String::from("Hello, rustlings!");
|
||||
let mut capture_by_mut = || {
|
||||
s.truncate(5); // Requires &mut String: also can be written as String::truncate(&mut s, 5);
|
||||
println!("{s}"); // This should print nothing (and a line break)
|
||||
// Since the "most" we need is mutable, it captures a single mutable reference to String.
|
||||
};
|
||||
capture_by_mut();
|
||||
|
||||
let mut s = String::from("Hello, rustlings!");
|
||||
let capture_by_ownership = || {
|
||||
s.truncate(5); // Requires &mut String
|
||||
println!("{s}"); // This should print nothing (and a line break)
|
||||
let boxed = s.into_boxed_str(); // Requires ownership: String::into_boxed_str(s);
|
||||
println!("{boxed}"); // This should print nothing (and a line break)
|
||||
};
|
||||
capture_by_ownership();
|
||||
|
||||
let s = String::from("Hello, rustlings!");
|
||||
let quiz = || {
|
||||
let captured_s = &s; // Using a shared reference fixes the issue.
|
||||
println!("Inside Closure quiz {captured_s}");
|
||||
};
|
||||
println!("Outside Closure quiz {s}");
|
||||
quiz();
|
||||
}
|
Loading…
Reference in a new issue