diff --git a/.all-contributorsrc b/.all-contributorsrc index 0df89f7e..f2033dcd 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1488,6 +1488,60 @@ "contributions": [ "code" ] + }, + { + "login": "nico-vromans", + "name": "Nico Vromans", + "avatar_url": "https://avatars.githubusercontent.com/u/48183857?v=4", + "profile": "https://github.com/nico-vromans", + "contributions": [ + "content" + ] + }, + { + "login": "vostok92", + "name": "vostok92", + "avatar_url": "https://avatars.githubusercontent.com/u/540339?v=4", + "profile": "https://github.com/vostok92", + "contributions": [ + "content" + ] + }, + { + "login": "magnusrodseth", + "name": "Magnus RΓΈdseth", + "avatar_url": "https://avatars.githubusercontent.com/u/59113973?v=4", + "profile": "http://magnusrodseth.vercel.app", + "contributions": [ + "content" + ] + }, + { + "login": "rubiesonthesky", + "name": "rubiesonthesky", + "avatar_url": "https://avatars.githubusercontent.com/u/2591240?v=4", + "profile": "https://github.com/rubiesonthesky", + "contributions": [ + "content" + ] + }, + { + "login": "GabrielBianconi", + "name": "Gabriel Bianconi", + "avatar_url": "https://avatars.githubusercontent.com/u/1275491?v=4", + "profile": "http://www.gabrielbianconi.com/", + "contributions": [ + "content" + ] + }, + { + "login": "Kodylow", + "name": "Kody Low", + "avatar_url": "https://avatars.githubusercontent.com/u/74332828?v=4", + "profile": "https://github.com/Kodylow", + "contributions": [ + "content" + ] } ], "contributorsPerLine": 8, diff --git a/AUTHORS.md b/AUTHORS.md index 9648f33c..8edf09bf 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -212,6 +212,12 @@ authors.
Brian Fakhoury

πŸ–‹
Markus Boehme

πŸ’» +
Nico Vromans

πŸ–‹ +
vostok92

πŸ–‹ +
Magnus RΓΈdseth

πŸ–‹ +
rubiesonthesky

πŸ–‹ +
Gabriel Bianconi

πŸ–‹ +
Kody Low

πŸ–‹ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aae3138..0b15cf99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,62 @@ + +## 5.2.0 (2022-08-27) + +#### Added + +- Added a `reset` command + +#### Changed + +- **options2**: Convert the exercise to use tests + +#### Fixed + +- **threads3**: Fixed a typo +- **quiz1**: Adjusted the explanations to be consistent with + the tests + + +## 5.1.1 (2022-08-17) + +#### Bug Fixes + +- Fixed an incorrect assertion in options1 + + +## 5.1.0 (2022-08-16) + +#### Features + +- Added a new `rc1` exercise. +- Added a new `cow1` exercise. + +#### Bug Fixes + +- **variables5**: Corrected reference to previous exercise +- **functions4**: Fixed line number reference +- **strings3**: Clarified comment wording +- **traits4, traits5**: Fixed line number reference +- **traits5**: + - Fixed typo in "parameter" + - Made exercise prefer a traits-based solution +- **lifetimes2**: Improved hint +- **threads3**: Fixed typo in hint +- **box1**: Replaced `unimplemented!` with `todo!` +- **errors5**: Provided an explanation for usage of `Box` +- **quiz2**: Fixed a typo +- **macros**: Updated the macros book link +- **options1**: + - Removed unused code + - Added more granular tests +- Fixed some comment syntax shenanigans in info.toml + +#### Housekeeping + +- Fixed a typo in .editorconfig +- Fixed a typo in integration_tests.rs +- Clarified manual installation instructions using `cargo install --path .` +- Added a link to our Zulip in the readme file + ## 5.0.0 (2022-07-16) diff --git a/Cargo.lock b/Cargo.lock index 862a8a2e..2370e8dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -459,7 +459,7 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "rustlings" -version = "5.0.0" +version = "5.1.1" dependencies = [ "argh", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 910b5389..25cd7bfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustlings" -version = "5.0.0" +version = "5.2.0" authors = ["Liv ", "Carol (Nichols || Goulding) "] edition = "2021" diff --git a/README.md b/README.md index afa55c86..2392ba9d 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,8 @@ If you get a permission denied message, you might have to exclude the directory Basically: Clone the repository at the latest tag, run `cargo install --path .`. ```bash -# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.0.0) -git clone -b 5.0.0 --depth 1 https://github.com/rust-lang/rustlings +# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.1.1) +git clone -b 5.1.1 --depth 1 https://github.com/rust-lang/rustlings cd rustlings cargo install --force --path . ``` @@ -154,6 +154,10 @@ Now you should be done! See [CONTRIBUTING.md](./CONTRIBUTING.md). +Development-focused discussion about Rustlings happens in the [**rustlings** stream](https://rust-lang.zulipchat.com/#narrow/stream/334454-rustlings) +on the [Rust Project Zulip](https://rust-lang.zulipchat.com). Feel free to start a new thread there +if you have ideas or suggestions! + ## Contributors ✨ Thanks goes to the wonderful people listed in [AUTHORS.md](./AUTHORS.md) πŸŽ‰ diff --git a/exercises/options/options1.rs b/exercises/options/options1.rs index 99c95911..d1735c2f 100644 --- a/exercises/options/options1.rs +++ b/exercises/options/options1.rs @@ -19,6 +19,7 @@ mod tests { #[test] fn check_icecream() { + assert_eq!(maybe_icecream(9), Some(5)); assert_eq!(maybe_icecream(10), Some(5)); assert_eq!(maybe_icecream(23), Some(0)); assert_eq!(maybe_icecream(22), Some(0)); diff --git a/exercises/options/options2.rs b/exercises/options/options2.rs index 75b66a37..b1120471 100644 --- a/exercises/options/options2.rs +++ b/exercises/options/options2.rs @@ -3,23 +3,34 @@ // I AM NOT DONE -fn main() { - let optional_word = Some(String::from("rustlings")); - // TODO: Make this an if let statement whose value is "Some" type - word = optional_word { - println!("The word is: {}", word); - } else { - println!("The optional word doesn't contain anything"); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_option() { + let target = "rustlings"; + let optional_target = Some(target); + + // TODO: Make this an if let statement whose value is "Some" type + word = optional_target { + assert_eq!(word, target); + } } - let mut optional_integers_vec: Vec> = Vec::new(); - for x in 1..10 { - optional_integers_vec.push(Some(x)); - } + #[test] + fn layered_option() { + let mut range = 10; + let mut optional_integers: Vec> = Vec::new(); + for i in 0..(range + 1) { + optional_integers.push(Some(i)); + } - // TODO: make this a while let statement - remember that vector.pop also adds another layer of Option - // You can stack `Option`'s into while let and if let - integer = optional_integers_vec.pop() { - println!("current value: {}", integer); + // TODO: make this a while let statement - remember that vector.pop also adds another layer of Option + // You can stack `Option`'s into while let and if let + integer = optional_integers.pop() { + assert_eq!(integer, range); + range -= 1; + } } } diff --git a/exercises/quiz1.rs b/exercises/quiz1.rs index 8d05b110..d1e76e59 100644 --- a/exercises/quiz1.rs +++ b/exercises/quiz1.rs @@ -5,7 +5,7 @@ // - If // Mary is buying apples. One apple usually costs 2 Rustbucks, but if you buy -// more than 40 at once, each apple only costs 1! Write a function that calculates +// 40 or more at once, each apple only costs 1! Write a function that calculates // the price of an order of apples given the quantity bought. No hints this time! // I AM NOT DONE diff --git a/exercises/standard_library_types/cow1.rs b/exercises/standard_library_types/cow1.rs new file mode 100644 index 00000000..5fba2519 --- /dev/null +++ b/exercises/standard_library_types/cow1.rs @@ -0,0 +1,48 @@ +// cow1.rs + +// This exercise explores the Cow, or Clone-On-Write type. +// Cow is a clone-on-write smart pointer. +// It can enclose and provide immutable access to borrowed data, and clone the data lazily when mutation or ownership is required. +// The type is designed to work with general borrowed data via the Borrow trait. + +// I AM NOT DONE + +use std::borrow::Cow; + +fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> { + for i in 0..input.len() { + let v = input[i]; + if v < 0 { + // Clones into a vector if not already owned. + input.to_mut()[i] = -v; + } + } + input +} + +fn main() { + // No clone occurs because `input` doesn't need to be mutated. + let slice = [0, 1, 2]; + let mut input = Cow::from(&slice[..]); + match abs_all(&mut input) { + Cow::Borrowed(_) => println!("I borrowed the slice!"), + _ => panic!("expected borrowed value"), + } + + // Clone occurs because `input` needs to be mutated. + let slice = [-1, 0, 1]; + let mut input = Cow::from(&slice[..]); + match abs_all(&mut input) { + Cow::Owned(_) => println!("I modified the slice and now own it!"), + _ => panic!("expected owned value"), + } + + // No clone occurs because `input` is already owned. + let slice = vec![-1, 0, 1]; + let mut input = Cow::from(slice); + match abs_all(&mut input) { + // TODO + Cow::Borrowed(_) => println!("I own this slice!"), + _ => panic!("expected borrowed value"), + } +} diff --git a/exercises/standard_library_types/rc1.rs b/exercises/standard_library_types/rc1.rs new file mode 100644 index 00000000..9b907fde --- /dev/null +++ b/exercises/standard_library_types/rc1.rs @@ -0,0 +1,98 @@ +// rc1.rs +// In this exercise, we want to express the concept of multiple owners via the Rc type. +// This is a model of our solar system - there is a Sun type and multiple Planets. +// The Planets take ownership of the sun, indicating that they revolve around the sun. + +// Make this code compile by using the proper Rc primitives to express that the sun has multiple owners. + +// I AM NOT DONE +use std::rc::Rc; + +#[derive(Debug)] +struct Sun {} + +#[derive(Debug)] +enum Planet { + Mercury(Rc), + Venus(Rc), + Earth(Rc), + Mars(Rc), + Jupiter(Rc), + Saturn(Rc), + Uranus(Rc), + Neptune(Rc), +} + +impl Planet { + fn details(&self) { + println!("Hi from {:?}!", self) + } +} + +fn main() { + let sun = Rc::new(Sun {}); + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + let mercury = Planet::Mercury(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + mercury.details(); + + let venus = Planet::Venus(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + venus.details(); + + let earth = Planet::Earth(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + earth.details(); + + let mars = Planet::Mars(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + mars.details(); + + let jupiter = Planet::Jupiter(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + jupiter.details(); + + // TODO + let saturn = Planet::Saturn(Rc::new(Sun {})); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + saturn.details(); + + // TODO + let uranus = Planet::Uranus(Rc::new(Sun {})); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + uranus.details(); + + // TODO + let neptune = Planet::Neptune(Rc::new(Sun {})); + println!("reference count = {}", Rc::strong_count(&sun)); // 9 references + neptune.details(); + + assert_eq!(Rc::strong_count(&sun), 9); + + drop(neptune); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + + drop(uranus); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + + drop(saturn); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + + drop(jupiter); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + + drop(mars); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + + // TODO + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + + // TODO + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + + // TODO + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + assert_eq!(Rc::strong_count(&sun), 1); +} diff --git a/info.toml b/info.toml index dc1485d7..060643d7 100644 --- a/info.toml +++ b/info.toml @@ -545,7 +545,7 @@ is the easiest, but how do you do it safely so that it doesn't panic in your fac [[exercises]] name = "options2" path = "exercises/options/options2.rs" -mode = "compile" +mode = "test" hint = """ check out: https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html @@ -932,6 +932,31 @@ is too much of a struggle, consider reading through all of Chapter 16 in the boo https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html """ +[[exercises]] +name = "rc1" +path = "exercises/standard_library_types/rc1.rs" +mode = "compile" +hint = """ +This is a straightforward exercise to use the Rc type. Each Planet has +ownership of the Sun, and uses Rc::clone() to increment the reference count of the Sun. +After using drop() to move the Planets out of scope individually, the reference count goes down. +In the end the sun only has one reference again, to itself. See more at: +https://doc.rust-lang.org/book/ch15-04-rc.html + +* Unforunately Pluto is no longer considered a planet :( +""" + +[[exercises]] +name = "cow1" +path = "exercises/standard_library_types/cow1.rs" +mode = "compile" +hint = """ +Since the vector is already owned, the `Cow` type doesn't need to clone it. + +Checkout https://doc.rust-lang.org/std/borrow/enum.Cow.html for documentation +on the `Cow` type. +""" + # THREADS [[exercises]] @@ -987,7 +1012,7 @@ hint = """ An alternate way to handle concurrency between threads is to use a mpsc (multiple producer, single consumer) channel to communicate. With both a sending end and a receiving end, it's possible to -send values in one thread and receieve them in another. +send values in one thread and receive them in another. Multiple producers are possible by using clone() to create a duplicate of the original sending end. See https://doc.rust-lang.org/book/ch16-02-message-passing.html for more info. diff --git a/src/main.rs b/src/main.rs index 0ddb7331..8eebc086 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use crate::exercise::{Exercise, ExerciseList}; use crate::project::RustAnalyzerProject; -use crate::run::run; +use crate::run::{reset, run}; use crate::verify::verify; use argh::FromArgs; use console::Emoji; @@ -26,7 +26,7 @@ mod run; mod verify; // In sync with crate version -const VERSION: &str = "5.0.0"; +const VERSION: &str = "5.2.0"; #[derive(FromArgs, PartialEq, Debug)] /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code @@ -47,6 +47,7 @@ enum Subcommands { Verify(VerifyArgs), Watch(WatchArgs), Run(RunArgs), + Reset(ResetArgs), Hint(HintArgs), List(ListArgs), Lsp(LspArgs), @@ -71,6 +72,15 @@ struct RunArgs { name: String, } +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand, name = "reset")] +/// Resets a single exercise using "git stash -- " +struct ResetArgs { + #[argh(positional)] + /// the name of the exercise + name: String, +} + #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "hint")] /// Returns a hint for the given exercise @@ -85,7 +95,6 @@ struct HintArgs { /// Enable rust-analyzer for exercises struct LspArgs {} - #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "list")] /// Lists the exercises available in Rustlings @@ -164,7 +173,9 @@ fn main() { "Pending" }; let solve_cond = { - (e.looks_done() && subargs.solved) || (!e.looks_done() && subargs.unsolved) || (!subargs.solved && !subargs.unsolved) + (e.looks_done() && subargs.solved) + || (!e.looks_done() && subargs.unsolved) + || (!subargs.solved && !subargs.unsolved) }; if solve_cond && (filter_cond || subargs.filter.is_none()) { let line = if subargs.paths { @@ -205,6 +216,12 @@ fn main() { run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1)); } + Subcommands::Reset(subargs) => { + let exercise = find_exercise(&subargs.name, &exercises); + + reset(exercise).unwrap_or_else(|_| std::process::exit(1)); + } + Subcommands::Hint(subargs) => { let exercise = find_exercise(&subargs.name, &exercises); @@ -212,7 +229,8 @@ fn main() { } Subcommands::Verify(_subargs) => { - verify(&exercises, (0, exercises.len()), verbose).unwrap_or_else(|_| std::process::exit(1)); + verify(&exercises, (0, exercises.len()), verbose) + .unwrap_or_else(|_| std::process::exit(1)); } Subcommands::Lsp(_subargs) => { @@ -236,12 +254,18 @@ fn main() { Subcommands::Watch(_subargs) => match watch(&exercises, verbose) { Err(e) => { - println!("Error: Could not watch your progress. Error message was {:?}.", e); + println!( + "Error: Could not watch your progress. Error message was {:?}.", + e + ); println!("Most likely you've run out of disk space or your 'inotify limit' has been reached."); std::process::exit(1); } Ok(WatchStatus::Finished) => { - println!("{emoji} All exercises completed! {emoji}", emoji = Emoji("πŸŽ‰", "β˜…")); + println!( + "{emoji} All exercises completed! {emoji}", + emoji = Emoji("πŸŽ‰", "β˜…") + ); println!("\n{}\n", FENISH_LINE); } Ok(WatchStatus::Unfinished) => { @@ -252,8 +276,10 @@ fn main() { } } - -fn spawn_watch_shell(failed_exercise_hint: &Arc>>, should_quit: Arc) { +fn spawn_watch_shell( + failed_exercise_hint: &Arc>>, + should_quit: Arc, +) { let failed_exercise_hint = Arc::clone(failed_exercise_hint); println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here."); thread::spawn(move || loop { @@ -290,16 +316,22 @@ fn spawn_watch_shell(failed_exercise_hint: &Arc>>, should_q fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise { if name.eq("next") { - exercises.iter().find(|e| !e.looks_done()).unwrap_or_else(|| { - println!("πŸŽ‰ Congratulations! You have done all the exercises!"); - println!("πŸ”š There are no more exercises to do next!"); - std::process::exit(1) - }) + exercises + .iter() + .find(|e| !e.looks_done()) + .unwrap_or_else(|| { + println!("πŸŽ‰ Congratulations! You have done all the exercises!"); + println!("πŸ”š There are no more exercises to do next!"); + std::process::exit(1) + }) } else { - exercises.iter().find(|e| e.name == name).unwrap_or_else(|| { - println!("No exercise found for '{}'!", name); - std::process::exit(1) - }) + exercises + .iter() + .find(|e| e.name == name) + .unwrap_or_else(|| { + println!("No exercise found for '{}'!", name); + std::process::exit(1) + }) } } @@ -337,8 +369,13 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result { let filepath = b.as_path().canonicalize().unwrap(); let pending_exercises = exercises .iter() - .find(|e| filepath.ends_with(&e.path)).into_iter() - .chain(exercises.iter().filter(|e| !e.looks_done() && !filepath.ends_with(&e.path))); + .find(|e| filepath.ends_with(&e.path)) + .into_iter() + .chain( + exercises + .iter() + .filter(|e| !e.looks_done() && !filepath.ends_with(&e.path)), + ); let num_done = exercises.iter().filter(|e| e.looks_done()).count(); clear_screen(); match verify(pending_exercises, (num_done, exercises.len()), verbose) { diff --git a/src/run.rs b/src/run.rs index eb558850..826f00a6 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,3 +1,5 @@ +use std::process::Command; + use crate::exercise::{Exercise, Mode}; use crate::verify::test; use indicatif::ProgressBar; @@ -15,6 +17,19 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> { Ok(()) } +// Resets the exercise by stashing the changes. +pub fn reset(exercise: &Exercise) -> Result<(), ()> { + let command = Command::new("git") + .args(["stash", "--"]) + .arg(&exercise.path) + .spawn(); + + match command { + Ok(_) => Ok(()), + Err(_) => Err(()), + } +} + // Invoke the rust compiler on the path of the given exercise // and run the ensuing binary. // This is strictly for non-test binaries, so output is displayed diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0be191f0..1a729232 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -110,6 +110,27 @@ fn run_single_test_no_exercise() { .code(1); } +#[test] +fn reset_single_exercise() { + Command::cargo_bin("rustlings") + .unwrap() + .args(&["reset", "intro1"]) + .assert() + .code(0); +} + +#[test] +fn reset_no_exercise() { + Command::cargo_bin("rustlings") + .unwrap() + .arg("reset") + .assert() + .code(1) + .stderr(predicates::str::contains( + "positional arguments not provided", + )); +} + #[test] fn get_hint_for_single_test() { Command::cargo_bin("rustlings") @@ -126,7 +147,7 @@ fn all_exercises_require_confirmation() { for exercise in glob("exercises/**/*.rs").unwrap() { let path = exercise.unwrap(); if path.file_name().unwrap() == "mod.rs" { - continue + continue; } let source = { let mut file = File::open(&path).unwrap();