diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 29a557a0..b4a876ed 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -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" }, diff --git a/exercises/18_closures/closures2.rs b/exercises/18_closures/closures2.rs new file mode 100644 index 00000000..15bc2b62 --- /dev/null +++ b/exercises/18_closures/closures2.rs @@ -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(); +} diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 72c9b4bb..5ebe5759 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -882,11 +882,23 @@ https://doc.rust-lang.org/book/ch11-01-writing-tests.html#checking-for-panics-wi [[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]] diff --git a/solutions/18_closures/closures2.rs b/solutions/18_closures/closures2.rs new file mode 100644 index 00000000..29858e39 --- /dev/null +++ b/solutions/18_closures/closures2.rs @@ -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(); +}