Compare commits

...

22 commits

Author SHA1 Message Date
Mo 47f8199a99
Merge pull request #2017 from Nahor/main
Fix misleading test name
2024-07-04 21:14:02 +02:00
mo8it 4bf0ddc0e1 Check exercises unsolved 2024-07-04 21:12:57 +02:00
Nahor 4cd0ccce83 Fix misleading test name 2024-07-04 11:58:09 -07:00
mo8it a3657188b6 Check for missing TODO comments 2024-07-04 20:28:46 +02:00
mo8it b8fcd11286 chore: Release 2024-07-04 20:02:43 +02:00
mo8it 4810555038 Update CHANGELOG 2024-07-04 20:02:30 +02:00
mo8it 84b291852c Update deps 2024-07-04 19:48:09 +02:00
mo8it 74831dd88f Add TODO 2024-07-04 19:46:43 +02:00
mo8it 0b220f9fff Fix clippy1 2024-07-04 19:46:43 +02:00
Mo d3cdeed871
Merge pull request #2015 from ramenhost/fix-move_semantics5
move_semantics5: change to fix misleading compiler error
2024-07-04 16:25:57 +02:00
Ramkumar 0524632199 fix move_semantics5 to change misleading compiler error 2024-07-04 18:48:09 +05:30
mo8it 30d5b7db92 Require solutions 2024-07-04 13:41:03 +02:00
mo8it 2f60f4d9ea Remove newline at the end of multiline strings 2024-07-04 13:38:57 +02:00
mo8it b017b87866 Fix typo 2024-07-04 13:38:41 +02:00
mo8it b87aa98634 Fix warnings 2024-07-04 13:38:35 +02:00
mo8it a4c07ca948 Improve the comment in intro1 2024-07-04 13:10:18 +02:00
mo8it b8826dd3b3 Remove comment about removing a semicolon 2024-07-04 13:08:59 +02:00
mo8it d54c050985 Improve a comment in errors2 2024-07-04 13:03:05 +02:00
mo8it 248dd4415e Add comment to options1 2024-07-04 13:00:04 +02:00
mo8it dec6772b05 Improve the comment of arc1 2024-07-04 12:58:04 +02:00
Mo b4f4c79ac4
Merge pull request #2012 from cbochs/main
fix: typo s/unwarp/unwrap/
2024-07-04 09:00:31 +02:00
Calvin Bochulak c1d5252b87
fix: typo s/unwarp/unwrap/ 2024-07-03 23:20:59 -06:00
35 changed files with 172 additions and 100 deletions

View file

@ -40,4 +40,4 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: swatinem/rust-cache@v2 - uses: swatinem/rust-cache@v2
- name: Run rustlings dev check - name: Run rustlings dev check
run: cargo run -- dev check run: cargo run -- dev check --require-solutions

View file

@ -1,3 +1,10 @@
<a name="6.0.1"></a>
## 6.0.1 (2024-07-04)
Small exercise improvements and fixes.
Most importantly, fixed that the exercise `clippy1` was already solved 😅
<a name="6.0.0"></a> <a name="6.0.0"></a>
## 6.0.0 (2024-07-03) ## 6.0.0 (2024-07-03)

62
Cargo.lock generated
View file

@ -136,9 +136,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "castaway" name = "castaway"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [ dependencies = [
"rustversion", "rustversion",
] ]
@ -527,7 +527,7 @@ dependencies = [
"libc", "libc",
"redox_syscall 0.5.2", "redox_syscall 0.5.2",
"smallvec", "smallvec",
"windows-targets 0.52.5", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -654,7 +654,7 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]] [[package]]
name = "rustlings" name = "rustlings"
version = "6.0.0" version = "6.0.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@ -673,7 +673,7 @@ dependencies = [
[[package]] [[package]]
name = "rustlings-macros" name = "rustlings-macros"
version = "6.0.0" version = "6.0.1"
dependencies = [ dependencies = [
"quote", "quote",
"serde", "serde",
@ -977,7 +977,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.52.5", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -997,18 +997,18 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.5", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.5", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.5", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.5", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.5", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.5", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.5", "windows_x86_64_msvc 0.52.6",
] ]
[[package]] [[package]]
@ -1019,9 +1019,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -1031,9 +1031,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -1043,15 +1043,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -1061,9 +1061,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -1073,9 +1073,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@ -1085,9 +1085,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -1097,9 +1097,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"

View file

@ -8,7 +8,7 @@ exclude = [
] ]
[workspace.package] [workspace.package]
version = "6.0.0" version = "6.0.1"
authors = [ authors = [
"Liv <mokou@fastmail.com>", "Liv <mokou@fastmail.com>",
"Mo Bitar <mo8it@proton.me>", "Mo Bitar <mo8it@proton.me>",
@ -53,7 +53,7 @@ hashbrown = "0.14.5"
notify-debouncer-mini = { version = "0.4.1", default-features = false } notify-debouncer-mini = { version = "0.4.1", default-features = false }
os_pipe = "1.2.0" os_pipe = "1.2.0"
ratatui = { version = "0.27.0", default-features = false, features = ["crossterm"] } ratatui = { version = "0.27.0", default-features = false, features = ["crossterm"] }
rustlings-macros = { path = "rustlings-macros", version = "=6.0.0" } rustlings-macros = { path = "rustlings-macros", version = "=6.0.1" }
serde_json = "1.0.120" serde_json = "1.0.120"
serde.workspace = true serde.workspace = true
toml_edit.workspace = true toml_edit.workspace = true

View file

@ -3,8 +3,7 @@
// ready for the next exercise, enter `n` in the terminal. // ready for the next exercise, enter `n` in the terminal.
// //
// The exercise file will be reloaded when you change one of the lines below! // The exercise file will be reloaded when you change one of the lines below!
// Try adding a new `println!`. // Try adding a new `println!` and check the updated output in the terminal.
// Try removing a semicolon and see what happens in the terminal!
fn main() { fn main() {
println!("Hello and"); println!("Hello and");

View file

@ -7,7 +7,7 @@ mod tests {
// TODO: Fix the compiler errors only by reordering the lines in the test. // TODO: Fix the compiler errors only by reordering the lines in the test.
// Don't add, change or remove any line. // Don't add, change or remove any line.
#[test] #[test]
fn move_semantics5() { fn move_semantics4() {
let mut x = 100; let mut x = 100;
let y = &mut x; let y = &mut x;
let z = &mut x; let z = &mut x;

View file

@ -1,3 +1,5 @@
#![allow(clippy::ptr_arg)]
// TODO: Fix the compiler errors without changing anything except adding or // TODO: Fix the compiler errors without changing anything except adding or
// removing references (the character `&`). // removing references (the character `&`).
@ -16,7 +18,7 @@ fn get_char(data: String) -> char {
// Should take ownership // Should take ownership
fn string_uppercase(mut data: &String) { fn string_uppercase(mut data: &String) {
data = &data.to_uppercase(); data = data.to_uppercase();
println!("{data}"); println!("{data}");
} }

View file

@ -1,3 +1,4 @@
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
enum Message { enum Message {
// TODO: Define the different variants used below. // TODO: Define the different variants used below.
@ -5,7 +6,7 @@ enum Message {
impl Message { impl Message {
fn call(&self) { fn call(&self) {
println!("{:?}", self); println!("{self:?}");
} }
} }

View file

@ -1,6 +1,7 @@
// You can bring module paths into scopes and provide new names for them with // You can bring module paths into scopes and provide new names for them with
// the `use` and `as` keywords. // the `use` and `as` keywords.
#[allow(dead_code)]
mod delicious_snacks { mod delicious_snacks {
// TODO: Add the following two `use` statements after fixing them. // TODO: Add the following two `use` statements after fixing them.
// use self::fruits::PEAR as ???; // use self::fruits::PEAR as ???;

View file

@ -19,7 +19,8 @@ mod tests {
// TODO: Fix this test. How do you get the value contained in the // TODO: Fix this test. How do you get the value contained in the
// Option? // Option?
let icecreams = maybe_icecream(12); let icecreams = maybe_icecream(12);
assert_eq!(icecreams, 5);
assert_eq!(icecreams, 5); // Don't change this line.
} }
#[test] #[test]

View file

@ -5,11 +5,11 @@
// the player typed in the quantity, we get it as a string. They might have // the player typed in the quantity, we get it as a string. They might have
// typed anything, not just numbers! // typed anything, not just numbers!
// //
// Right now, this function isn't handling the error case at all (and isn't // Right now, this function isn't handling the error case at all. What we want
// handling the success case properly either). What we want to do is: If we call // to do is: If we call the `total_cost` function on a string that is not a
// the `total_cost` function on a string that is not a number, that function // number, that function will return a `ParseIntError`. In that case, we want to
// will return a `ParseIntError`. In that case, we want to immediately return // immediately return that error from our function and not try to multiply and
// that error from our function and not try to multiply and add. // add.
// //
// There are at least two ways to implement this that are both correct. But one // There are at least two ways to implement this that are both correct. But one
// is a lot shorter! // is a lot shorter!

View file

@ -1,3 +1,5 @@
#![allow(clippy::comparison_chain)]
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum CreationError { enum CreationError {
Negative, Negative,

View file

@ -35,7 +35,7 @@ impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> { fn new(value: i64) -> Result<Self, CreationError> {
match value { match value {
x if x < 0 => Err(CreationError::Negative), x if x < 0 => Err(CreationError::Negative),
x if x == 0 => Err(CreationError::Zero), 0 => Err(CreationError::Zero),
x => Ok(Self(x as u64)), x => Ok(Self(x as u64)),
} }
} }
@ -55,7 +55,6 @@ fn main() {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use std::num::IntErrorKind;
#[test] #[test]
fn test_parse_error() { fn test_parse_error() {

View file

@ -1,3 +1,5 @@
#![allow(dead_code)]
trait Licensed { trait Licensed {
// TODO: Add a default implementation for `licensing_info` so that // TODO: Add a default implementation for `licensing_info` so that
// implementors like the two structs below can share that default behavior // implementors like the two structs below can share that default behavior

View file

@ -54,7 +54,7 @@ mod tests {
#[test] #[test]
fn test_result_with_list() { fn test_result_with_list() {
assert_eq!(result_with_list().unwarp(), [1, 11, 1426, 3]); assert_eq!(result_with_list().unwrap(), [1, 11, 1426, 3]);
} }
#[test] #[test]

View file

@ -1,4 +1,4 @@
// In this exercise, we are given a `Vec` of u32 called `numbers` with values // In this exercise, we are given a `Vec` of `u32` called `numbers` with values
// ranging from 0 to 99. We would like to use this set of numbers within 8 // ranging from 0 to 99. We would like to use this set of numbers within 8
// different threads simultaneously. Each thread is going to get the sum of // different threads simultaneously. Each thread is going to get the sum of
// every eighth value with an offset. // every eighth value with an offset.
@ -9,8 +9,11 @@
// … // …
// The eighth thread (offset 7), will sum 7, 15, 23, … // The eighth thread (offset 7), will sum 7, 15, 23, …
// //
// Because we are using threads, our values need to be thread-safe. Therefore, // Each thread should own a reference-counting pointer to the vector of
// we are using `Arc`. // numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`.
//
// Don't get distracted by how threads are spawned and joined. We will practice
// that later in the exercises about threads.
// Don't change the lines below. // Don't change the lines below.
#![forbid(unused_imports)] #![forbid(unused_imports)]

View file

@ -8,6 +8,7 @@ use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
struct Sun; struct Sun;
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
enum Planet { enum Planet {
Mercury(Rc<Sun>), Mercury(Rc<Sun>),

View file

@ -4,11 +4,9 @@
// For these exercises, the code will fail to compile when there are Clippy // For these exercises, the code will fail to compile when there are Clippy
// warnings. Check Clippy's suggestions from the output to solve the exercise. // warnings. Check Clippy's suggestions from the output to solve the exercise.
use std::f32::consts::PI;
fn main() { fn main() {
// Use the more accurate `PI` constant. // TODO: Fix the Clippy lint in this line.
let pi = PI; let pi = 3.14;
let radius: f32 = 5.0; let radius: f32 = 5.0;
let area = pi * radius.powi(2); let area = pi * radius.powi(2);

View file

@ -6,8 +6,8 @@
// Mary is buying apples. The price of an apple is calculated as follows: // Mary is buying apples. The price of an apple is calculated as follows:
// - An apple costs 2 rustbucks. // - An apple costs 2 rustbucks.
// - If Mary buys more than 40 apples, each apple only costs 1 rustbuck! // - If Mary buys more than 40 apples, each apple only costs 1 rustbuck!
// Write a function that calculates the price of an order of apples given the // TODO: Write a function that calculates the price of an order of apples given
// quantity bought. // the quantity bought.
// Put your function here! // Put your function here!
// fn calculate_price_of_apples(???) -> ??? { // fn calculate_price_of_apples(???) -> ??? {

View file

@ -16,16 +16,14 @@ get started, here are some notes about how Rustlings operates:
3. If you're stuck on an exercise, enter `h` to show a hint. 3. If you're stuck on an exercise, enter `h` to show a hint.
4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub! 4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub!
(https://github.com/rust-lang/rustlings). We look at every issue, and sometimes, (https://github.com/rust-lang/rustlings). We look at every issue, and sometimes,
other learners do too so you can help each other out! other learners do too so you can help each other out!"""
"""
final_message = """We hope you enjoyed learning about the various aspects of Rust! final_message = """We hope you enjoyed learning about the various aspects of Rust!
If you noticed any issues, don't hesitate to report them on Github. If you noticed any issues, don't hesitate to report them on Github.
You can also contribute your own exercises to help the greater community! You can also contribute your own exercises to help the greater community!
Before reporting an issue or contributing, please read our guidelines: Before reporting an issue or contributing, please read our guidelines:
https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md"""
"""
# INTRO # INTRO
@ -33,6 +31,7 @@ https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md
name = "intro1" name = "intro1"
dir = "00_intro" dir = "00_intro"
test = false test = false
skip_check_unsolved = true
hint = """ hint = """
Enter `n` to move on to the next exercise. Enter `n` to move on to the next exercise.
You might need to press ENTER after typing `n`.""" You might need to press ENTER after typing `n`."""
@ -130,8 +129,7 @@ The type of Constants must always be annotated.
Read more about constants and the differences between variables and constants Read more about constants and the differences between variables and constants
under 'Constants' in the book's section 'Variables and Mutability': under 'Constants' in the book's section 'Variables and Mutability':
https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants"""
"""
# FUNCTIONS # FUNCTIONS
@ -312,8 +310,7 @@ In Rust, there are two ways to define a Vector.
the initial values. the initial values.
Check this chapter: https://doc.rust-lang.org/stable/book/ch08-01-vectors.html Check this chapter: https://doc.rust-lang.org/stable/book/ch08-01-vectors.html
of the Rust book to learn more. of the Rust book to learn more."""
"""
[[exercises]] [[exercises]]
name = "vecs2" name = "vecs2"
@ -327,8 +324,7 @@ In the second function, we map the values of the input and collect them into a v
After you've completed both functions, decide for yourself which approach you After you've completed both functions, decide for yourself which approach you
like better. like better.
What do you think is the more commonly used pattern under Rust developers? What do you think is the more commonly used pattern under Rust developers?"""
"""
# MOVE SEMANTICS # MOVE SEMANTICS
@ -355,8 +351,7 @@ We call this "moving" a variable. When we pass `vec0` into `fill_vec`, it's
being "moved" into `vec1`, meaning we can't access `vec0` anymore. being "moved" into `vec1`, meaning we can't access `vec0` anymore.
You could make another, separate version of the data that's in `vec0` and You could make another, separate version of the data that's in `vec0` and
pass it to `fill_vec` instead. pass it to `fill_vec` instead."""
"""
[[exercises]] [[exercises]]
name = "move_semantics3" name = "move_semantics3"
@ -375,8 +370,7 @@ Carefully reason about the range in which each mutable reference is in
scope. Does it help to update the value of `x` immediately after scope. Does it help to update the value of `x` immediately after
the mutable reference is taken? the mutable reference is taken?
Read more about 'Mutable References' in the book's section 'References and Borrowing': Read more about 'Mutable References' in the book's section 'References and Borrowing':
https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references. https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references."""
"""
[[exercises]] [[exercises]]
name = "move_semantics5" name = "move_semantics5"
@ -517,8 +511,7 @@ Example:
`placeholder("blue");` `placeholder("blue");`
should become should become
`string_slice("blue");` `string_slice("blue");`
because "blue" is `&str`, not `String`. because "blue" is `&str`, not `String`."""
"""
# MODULES # MODULES
@ -620,8 +613,7 @@ Remember that `Option`s can be nested in if-let and while-let statements.
For example: `if let Some(Some(x)) = y` For example: `if let Some(Some(x)) = y`
Also see `Option::flatten` Also see `Option::flatten`"""
"""
[[exercises]] [[exercises]]
name = "options3" name = "options3"
@ -813,8 +805,7 @@ Here is how to specify a trait bound for an implementation block:
`impl<T: Trait1 + Trait2 + > for Foo<T> { }` `impl<T: Trait1 + Trait2 + > for Foo<T> { }`
You may need this: You may need this:
`use std::fmt::Display;` `use std::fmt::Display;`"""
"""
# LIFETIMES # LIFETIMES

View file

@ -1,3 +1,5 @@
#![allow(clippy::needless_late_init)]
fn main() { fn main() {
// Reading uninitialized variables isn't allowed in Rust! // Reading uninitialized variables isn't allowed in Rust!
// Therefore, we need to assign a value first. // Therefore, we need to assign a value first.
@ -5,7 +7,7 @@ fn main() {
println!("Number {x}"); println!("Number {x}");
// It possible to declare a variable and initialize it later. // It is possible to declare a variable and initialize it later.
// But it can't be used before initialization. // But it can't be used before initialization.
let y: i32; let y: i32;
y = 42; y = 42;

View file

@ -1,3 +1,5 @@
#![allow(clippy::ptr_arg)]
fn main() { fn main() {
let data = "Rust is great!".to_string(); let data = "Rust is great!".to_string();

View file

@ -1,3 +1,4 @@
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
enum Message { enum Message {
Move { x: i64, y: i64 }, Move { x: i64, y: i64 },
@ -8,7 +9,7 @@ enum Message {
impl Message { impl Message {
fn call(&self) { fn call(&self) {
println!("{:?}", self); println!("{self:?}");
} }
} }

View file

@ -1,3 +1,4 @@
#[allow(dead_code)]
mod delicious_snacks { mod delicious_snacks {
// Added `pub` and used the expected alias after `as`. // Added `pub` and used the expected alias after `as`.
pub use self::fruits::PEAR as fruit; pub use self::fruits::PEAR as fruit;

View file

@ -28,13 +28,13 @@ fn build_scores_table(results: &str) -> HashMap<&str, Team> {
let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap(); let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap();
// Insert the default with zeros if a team doesn't exist yet. // Insert the default with zeros if a team doesn't exist yet.
let mut team_1 = scores.entry(team_1_name).or_insert_with(|| Team::default()); let team_1 = scores.entry(team_1_name).or_insert_with(Team::default);
// Update the values. // Update the values.
team_1.goals_scored += team_1_score; team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score; team_1.goals_conceded += team_2_score;
// Similarely for the second team. // Similarely for the second team.
let mut team_2 = scores.entry(team_2_name).or_insert_with(|| Team::default()); let team_2 = scores.entry(team_2_name).or_insert_with(Team::default);
team_2.goals_scored += team_2_score; team_2.goals_scored += team_2_score;
team_2.goals_conceded += team_1_score; team_2.goals_conceded += team_1_score;
} }

View file

@ -22,6 +22,7 @@ mod tests {
fn raw_value() { fn raw_value() {
// Using `unwrap` is fine in a test. // Using `unwrap` is fine in a test.
let icecreams = maybe_icecream(12).unwrap(); let icecreams = maybe_icecream(12).unwrap();
assert_eq!(icecreams, 5); assert_eq!(icecreams, 5);
} }

View file

@ -5,17 +5,18 @@
// the player typed in the quantity, we get it as a string. They might have // the player typed in the quantity, we get it as a string. They might have
// typed anything, not just numbers! // typed anything, not just numbers!
// //
// Right now, this function isn't handling the error case at all (and isn't // Right now, this function isn't handling the error case at all. What we want
// handling the success case properly either). What we want to do is: If we call // to do is: If we call the `total_cost` function on a string that is not a
// the `total_cost` function on a string that is not a number, that function // number, that function will return a `ParseIntError`. In that case, we want to
// will return a `ParseIntError`. In that case, we want to immediately return // immediately return that error from our function and not try to multiply and
// that error from our function and not try to multiply and add. // add.
// //
// There are at least two ways to implement this that are both correct. But one // There are at least two ways to implement this that are both correct. But one
// is a lot shorter! // is a lot shorter!
use std::num::ParseIntError; use std::num::ParseIntError;
#[allow(unused_variables)]
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
let processing_fee = 1; let processing_fee = 1;
let cost_per_item = 5; let cost_per_item = 5;

View file

@ -1,3 +1,5 @@
#![allow(clippy::comparison_chain)]
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum CreationError { enum CreationError {
Negative, Negative,

View file

@ -36,7 +36,7 @@ impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> { fn new(value: i64) -> Result<Self, CreationError> {
match value { match value {
x if x < 0 => Err(CreationError::Negative), x if x < 0 => Err(CreationError::Negative),
x if x == 0 => Err(CreationError::Zero), 0 => Err(CreationError::Zero),
x => Ok(Self(x as u64)), x => Ok(Self(x as u64)),
} }
} }
@ -57,7 +57,6 @@ fn main() {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use std::num::IntErrorKind;
#[test] #[test]
fn test_parse_error() { fn test_parse_error() {

View file

@ -1,3 +1,5 @@
#![allow(dead_code)]
trait Licensed { trait Licensed {
fn licensing_info(&self) -> String { fn licensing_info(&self) -> String {
"Default license".to_string() "Default license".to_string()

View file

@ -1,4 +1,4 @@
// In this exercise, we are given a `Vec` of u32 called `numbers` with values // In this exercise, we are given a `Vec` of `u32` called `numbers` with values
// ranging from 0 to 99. We would like to use this set of numbers within 8 // ranging from 0 to 99. We would like to use this set of numbers within 8
// different threads simultaneously. Each thread is going to get the sum of // different threads simultaneously. Each thread is going to get the sum of
// every eighth value with an offset. // every eighth value with an offset.
@ -9,8 +9,11 @@
// … // …
// The eighth thread (offset 7), will sum 7, 15, 23, … // The eighth thread (offset 7), will sum 7, 15, 23, …
// //
// Because we are using threads, our values need to be thread-safe. Therefore, // Each thread should own a reference-counting pointer to the vector of
// we are using `Arc`. // numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`.
//
// Don't get distracted by how threads are spawned and joined. We will practice
// that later in the exercises about threads.
// Don't change the lines below. // Don't change the lines below.
#![forbid(unused_imports)] #![forbid(unused_imports)]

View file

@ -8,6 +8,7 @@ use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
struct Sun; struct Sun;
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
enum Planet { enum Planet {
Mercury(Rc<Sun>), Mercury(Rc<Sun>),

View file

@ -4,6 +4,7 @@
// type itself. You can read more about it in the documentation: // type itself. You can read more about it in the documentation:
// https://doc.rust-lang.org/std/convert/trait.TryFrom.html // https://doc.rust-lang.org/std/convert/trait.TryFrom.html
#![allow(clippy::useless_vec)]
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]

View file

@ -92,6 +92,10 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<hashbrown::HashSet<
bail!("The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors"); bail!("The `main` function is missing in the file `{path}`.\nCreate at least an empty `main` function to avoid language server errors");
} }
if !file_buf.contains("// TODO") {
bail!("Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user.");
}
if !exercise_info.test && file_buf.contains("#[test]") { if !exercise_info.test && file_buf.contains("#[test]") {
bail!("The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file"); bail!("The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file");
} }
@ -158,7 +162,46 @@ fn check_unexpected_files(
Ok(()) Ok(())
} }
fn check_exercises(info_file: &InfoFile) -> Result<()> { fn check_exercises_unsolved(info_file: &InfoFile, target_dir: &Path) -> Result<()> {
let error_occurred = AtomicBool::new(false);
println!(
"Running all exercises to check that they aren't already solved. This may take a while…\n",
);
thread::scope(|s| {
for exercise_info in &info_file.exercises {
if exercise_info.skip_check_unsolved {
continue;
}
s.spawn(|| {
let error = |e| {
let mut stderr = io::stderr().lock();
stderr.write_all(e).unwrap();
stderr.write_all(b"\nProblem with the exercise ").unwrap();
stderr.write_all(exercise_info.name.as_bytes()).unwrap();
stderr.write_all(SEPARATOR).unwrap();
error_occurred.store(true, atomic::Ordering::Relaxed);
};
let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
match exercise_info.run_exercise(&mut output, target_dir) {
Ok(true) => error(b"Already solved!"),
Ok(false) => (),
Err(e) => error(e.to_string().as_bytes()),
}
});
}
});
if error_occurred.load(atomic::Ordering::Relaxed) {
bail!(CHECK_EXERCISES_UNSOLVED_ERR);
}
Ok(())
}
fn check_exercises(info_file: &InfoFile, target_dir: &Path) -> Result<()> {
match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) { match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) {
Ordering::Less => bail!("`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"), Ordering::Less => bail!("`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"),
Ordering::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"), Ordering::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"),
@ -168,15 +211,14 @@ fn check_exercises(info_file: &InfoFile) -> Result<()> {
let info_file_paths = check_info_file_exercises(info_file)?; let info_file_paths = check_info_file_exercises(info_file)?;
check_unexpected_files("exercises", &info_file_paths)?; check_unexpected_files("exercises", &info_file_paths)?;
Ok(()) check_exercises_unsolved(info_file, target_dir)
} }
fn check_solutions(require_solutions: bool, info_file: &InfoFile) -> Result<()> { fn check_solutions(require_solutions: bool, info_file: &InfoFile, target_dir: &Path) -> Result<()> {
let target_dir = parse_target_dir()?;
let paths = Mutex::new(hashbrown::HashSet::with_capacity(info_file.exercises.len())); let paths = Mutex::new(hashbrown::HashSet::with_capacity(info_file.exercises.len()));
let error_occurred = AtomicBool::new(false); let error_occurred = AtomicBool::new(false);
println!("Running all solutions. This may take a while...\n"); println!("Running all solutions. This may take a while\n");
thread::scope(|s| { thread::scope(|s| {
for exercise_info in &info_file.exercises { for exercise_info in &info_file.exercises {
s.spawn(|| { s.spawn(|| {
@ -202,7 +244,7 @@ fn check_solutions(require_solutions: bool, info_file: &InfoFile) -> Result<()>
} }
let mut output = Vec::with_capacity(OUTPUT_CAPACITY); let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
match exercise_info.run_solution(&mut output, &target_dir) { match exercise_info.run_solution(&mut output, target_dir) {
Ok(true) => { Ok(true) => {
paths.lock().unwrap().insert(PathBuf::from(path)); paths.lock().unwrap().insert(PathBuf::from(path));
} }
@ -238,8 +280,9 @@ pub fn check(require_solutions: bool) -> Result<()> {
check_cargo_toml(&info_file.exercises, &current_cargo_toml, b"")?; check_cargo_toml(&info_file.exercises, &current_cargo_toml, b"")?;
} }
check_exercises(&info_file)?; let target_dir = parse_target_dir()?;
check_solutions(require_solutions, &info_file)?; check_exercises(&info_file, &target_dir)?;
check_solutions(require_solutions, &info_file, &target_dir)?;
println!("\nEverything looks fine!"); println!("\nEverything looks fine!");
@ -248,3 +291,6 @@ pub fn check(require_solutions: bool) -> Result<()> {
const SEPARATOR: &[u8] = const SEPARATOR: &[u8] =
b"\n========================================================================================\n"; b"\n========================================================================================\n";
const CHECK_EXERCISES_UNSOLVED_ERR: &str = "At least one exercise is already solved or failed to run. See the output above.
If this is an intro exercise that is intended to be already solved, add `skip_check_unsolved = true` to the exercise's metadata in the `info.toml` file.";

View file

@ -11,14 +11,17 @@ pub struct ExerciseInfo {
pub name: String, pub name: String,
/// Exercise's directory name inside the `exercises/` directory. /// Exercise's directory name inside the `exercises/` directory.
pub dir: Option<String>, pub dir: Option<String>,
#[serde(default = "default_true")]
/// Run `cargo test` on the exercise. /// Run `cargo test` on the exercise.
#[serde(default = "default_true")]
pub test: bool, pub test: bool,
/// Deny all Clippy warnings. /// Deny all Clippy warnings.
#[serde(default)] #[serde(default)]
pub strict_clippy: bool, pub strict_clippy: bool,
/// The exercise's hint to be shown to the user on request. /// The exercise's hint to be shown to the user on request.
pub hint: String, pub hint: String,
/// The exercise is already solved. Ignore it when checking that all exercises are unsolved.
#[serde(default)]
pub skip_check_unsolved: bool,
} }
#[inline(always)] #[inline(always)]
const fn default_true() -> bool { const fn default_true() -> bool {