Compare commits

..

No commits in common. "09c94bef2dbaf44daf81d8f618289c9425d1f90f" and "663a03a17b2d2001f4f3f35a59cd2e2aa5f2bb24" have entirely different histories.

21 changed files with 81 additions and 326 deletions

View file

@ -3,35 +3,31 @@
// wait until all the spawned threads have finished and should collect their // wait until all the spawned threads have finished and should collect their
// return values into a vector. // return values into a vector.
use std::{ use std::thread;
thread, use std::time::{Duration, Instant};
time::{Duration, Instant},
};
fn main() { fn main() {
let mut handles = Vec::new(); let mut handles = vec![];
for i in 0..10 { for i in 0..10 {
let handle = thread::spawn(move || { handles.push(thread::spawn(move || {
let start = Instant::now(); let start = Instant::now();
thread::sleep(Duration::from_millis(250)); thread::sleep(Duration::from_millis(250));
println!("Thread {i} done"); println!("thread {} is complete", i);
start.elapsed().as_millis() start.elapsed().as_millis()
}); }));
handles.push(handle);
} }
let mut results = Vec::new(); let mut results: Vec<u128> = vec![];
for handle in handles { for handle in handles {
// TODO: Collect the results of all threads into the `results` vector. // TODO: a struct is returned from thread::spawn, can you use it?
// Use the `JoinHandle` struct which is returned by `thread::spawn`.
} }
if results.len() != 10 { if results.len() != 10 {
panic!("Oh no! Some thread isn't done yet!"); panic!("Oh no! All the spawned threads did not finish!");
} }
println!(); println!();
for (i, result) in results.into_iter().enumerate() { for (i, result) in results.into_iter().enumerate() {
println!("Thread {i} took {result}ms"); println!("thread {} took {}ms", i, result);
} }
} }

View file

@ -1,34 +1,35 @@
// Building on the last exercise, we want all of the threads to complete their // Building on the last exercise, we want all of the threads to complete their
// work. But this time, the spawned threads need to be in charge of updating a // work but this time the spawned threads need to be in charge of updating a
// shared value: `JobStatus.jobs_done` // shared value: JobStatus.jobs_completed
use std::{sync::Arc, thread, time::Duration}; use std::sync::Arc;
use std::thread;
use std::time::Duration;
struct JobStatus { struct JobStatus {
jobs_done: u32, jobs_completed: u32,
} }
fn main() { fn main() {
// TODO: `Arc` isn't enough if you want a **mutable** shared state. // TODO: `Arc` isn't enough if you want a **mutable** shared state
let status = Arc::new(JobStatus { jobs_done: 0 }); let status = Arc::new(JobStatus { jobs_completed: 0 });
let mut handles = Vec::new(); let mut handles = vec![];
for _ in 0..10 { for _ in 0..10 {
let status_shared = Arc::clone(&status); let status_shared = Arc::clone(&status);
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(250)); thread::sleep(Duration::from_millis(250));
// TODO: You must take an action before you update a shared value
// TODO: You must take an action before you update a shared value. status_shared.jobs_completed += 1;
status_shared.jobs_done += 1;
}); });
handles.push(handle); handles.push(handle);
} }
// Waiting for all jobs to complete. // Waiting for all jobs to complete
for handle in handles { for handle in handles {
handle.join().unwrap(); handle.join().unwrap();
} }
// TODO: Print the value of `JobStatus.jobs_done`. // TODO: Print the value of `JobStatus.jobs_completed`
println!("Jobs done: {}", todo!()); println!("Jobs completed: {}", ???);
} }

View file

@ -1,4 +1,7 @@
use std::{sync::mpsc, thread, time::Duration}; use std::sync::mpsc;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
struct Queue { struct Queue {
length: u32, length: u32,
@ -8,7 +11,7 @@ struct Queue {
impl Queue { impl Queue {
fn new() -> Self { fn new() -> Self {
Self { Queue {
length: 10, length: 10,
first_half: vec![1, 2, 3, 4, 5], first_half: vec![1, 2, 3, 4, 5],
second_half: vec![6, 7, 8, 9, 10], second_half: vec![6, 7, 8, 9, 10],
@ -16,22 +19,20 @@ impl Queue {
} }
} }
fn send_tx(q: Queue, tx: mpsc::Sender<u32>) { fn send_tx(q: Queue, tx: mpsc::Sender<u32>) -> () {
// TODO: We want to send `tx` to both threads. But currently, it is moved
// into the frist thread. How could you solve this problem?
thread::spawn(move || { thread::spawn(move || {
for val in q.first_half { for val in q.first_half {
println!("Sending {val:?}"); println!("sending {:?}", val);
tx.send(val).unwrap(); tx.send(val).unwrap();
thread::sleep(Duration::from_millis(250)); thread::sleep(Duration::from_secs(1));
} }
}); });
thread::spawn(move || { thread::spawn(move || {
for val in q.second_half { for val in q.second_half {
println!("Sending {val:?}"); println!("sending {:?}", val);
tx.send(val).unwrap(); tx.send(val).unwrap();
thread::sleep(Duration::from_millis(250)); thread::sleep(Duration::from_secs(1));
} }
}); });
} }
@ -54,11 +55,11 @@ mod tests {
let mut total_received: u32 = 0; let mut total_received: u32 = 0;
for received in rx { for received in rx {
println!("Got: {received}"); println!("Got: {}", received);
total_received += 1; total_received += 1;
} }
println!("Number of received values: {total_received}"); println!("total numbers received: {}", total_received);
assert_eq!(total_received, queue_length); assert_eq!(total_received, queue_length);
} }
} }

View file

@ -5,6 +5,5 @@ macro_rules! my_macro {
} }
fn main() { fn main() {
// TODO: Fix the macro call.
my_macro(); my_macro();
} }

View file

@ -2,7 +2,6 @@ fn main() {
my_macro!(); my_macro!();
} }
// TODO: Fix the compiler error by moving the whole definition of this macro.
macro_rules! my_macro { macro_rules! my_macro {
() => { () => {
println!("Check out my macro!"); println!("Check out my macro!");

View file

@ -1,5 +1,5 @@
// TODO: Fix the compiler error without taking the macro definition out of this // Make me compile, without taking the macro out of the module!
// module.
mod macros { mod macros {
macro_rules! my_macro { macro_rules! my_macro {
() => { () => {

View file

@ -1,4 +1,3 @@
// TODO: Fix the compiler error by adding one or two characters.
#[rustfmt::skip] #[rustfmt::skip]
macro_rules! my_macro { macro_rules! my_macro {
() => { () => {

View file

@ -1,17 +1,19 @@
// The Clippy tool is a collection of lints to analyze your code so you can // The Clippy tool is a collection of lints to analyze your code so you can
// catch common mistakes and improve your Rust code. // catch common mistakes and improve your Rust code.
// //
// 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; use std::f32;
fn main() { fn main() {
// Use the more accurate `PI` constant. let pi = 3.14f32;
let pi = PI; let radius = 5.00f32;
let radius: f32 = 5.0;
let area = pi * radius.powi(2); let area = pi * f32::powi(radius, 2);
println!("The area of a circle with radius {radius:.2} is {area:.5}"); println!(
"The area of a circle with radius {:.2} is {:.5}!",
radius, area
)
} }

View file

@ -1,10 +1,8 @@
fn main() { fn main() {
let mut res = 42; let mut res = 42;
let option = Some(12); let option = Some(12);
// TODO: Fix the Clippy lint.
for x in option { for x in option {
res += x; res += x;
} }
println!("{}", res);
println!("{res}");
} }

View file

@ -1,27 +1,25 @@
// Here are some more easy Clippy fixes so you can see its utility 📎 // Here's a couple more easy Clippy fixes, so you can see its utility.
// TODO: Fix all the Clippy lints.
#[rustfmt::skip]
#[allow(unused_variables, unused_assignments)] #[allow(unused_variables, unused_assignments)]
fn main() { fn main() {
let my_option: Option<()> = None; let my_option: Option<()> = None;
if my_option.is_none() { if my_option.is_none() {
println!("{:?}", my_option.unwrap()); my_option.unwrap();
} }
let my_arr = &[ let my_arr = &[
-1, -2, -3 -1, -2, -3
-4, -5, -6 -4, -5, -6
]; ];
println!("My array! Here it is: {my_arr:?}"); println!("My array! Here it is: {:?}", my_arr);
let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5);
println!("This Vec is empty, see? {my_empty_vec:?}"); println!("This Vec is empty, see? {:?}", my_empty_vec);
let mut value_a = 45; let mut value_a = 45;
let mut value_b = 66; let mut value_b = 66;
// Let's swap these two! // Let's swap these two!
value_a = value_b; value_a = value_b;
value_b = value_a; value_b = value_a;
println!("value a: {value_a}; value b: {value_b}"); println!("value a: {}; value b: {}", value_a, value_b);
} }

View file

@ -1037,7 +1037,7 @@ hint = """
https://doc.rust-lang.org/std/thread/fn.spawn.html https://doc.rust-lang.org/std/thread/fn.spawn.html
A challenge with multi-threaded applications is that the main thread can A challenge with multi-threaded applications is that the main thread can
finish before the spawned threads are done. finish before the spawned threads are completed.
https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles
Use the `JoinHandle`s to wait for each thread to finish and collect their Use the `JoinHandle`s to wait for each thread to finish and collect their
@ -1051,19 +1051,19 @@ dir = "20_threads"
test = false test = false
hint = """ hint = """
`Arc` is an Atomic Reference Counted pointer that allows safe, shared access `Arc` is an Atomic Reference Counted pointer that allows safe, shared access
to **immutable** data. But we want to *change* the number of `jobs_done` so to **immutable** data. But we want to *change* the number of `jobs_completed`
we'll need to also use another type that will only allow one thread to mutate so we'll need to also use another type that will only allow one thread to
the data at a time. Take a look at this section of the book: mutate the data at a time. Take a look at this section of the book:
https://doc.rust-lang.org/book/ch16-03-shared-state.html#atomic-reference-counting-with-arct https://doc.rust-lang.org/book/ch16-03-shared-state.html#atomic-reference-counting-with-arct
Keep reading if you'd like more hints :) Keep reading if you'd like more hints :)
Do you now have an `Arc<Mutex<JobStatus>>` at the beginning of `main`? Like: Do you now have an `Arc<Mutex<JobStatus>>` at the beginning of `main`? Like:
``` ```
let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
``` ```
Similar to the code in the following example in The Book: Similar to the code in the following example in the book:
https://doc.rust-lang.org/book/ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads""" https://doc.rust-lang.org/book/ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads"""
[[exercises]] [[exercises]]
@ -1076,11 +1076,10 @@ An alternate way to handle concurrency between threads is to use an `mpsc`
With both a sending end and a receiving end, it's possible to send values in With both a sending end and a receiving end, it's possible to send values in
one thread and receive them in another. one thread and receive them in another.
Multiple producers are possible by using `clone()` to create a duplicate of the Multiple producers are possible by using clone() to create a duplicate of the
original sending end. original sending end.
Related section in The Book: See https://doc.rust-lang.org/book/ch16-02-message-passing.html for more info."""
https://doc.rust-lang.org/book/ch16-02-message-passing.html"""
# MACROS # MACROS
@ -1089,8 +1088,9 @@ name = "macros1"
dir = "21_macros" dir = "21_macros"
test = false test = false
hint = """ hint = """
When you call a macro, you need to add something special compared to a regular When you call a macro, you need to add something special compared to a
function call.""" regular function call. If you're stuck, take a look at what's inside
`my_macro`."""
[[exercises]] [[exercises]]
name = "macros2" name = "macros2"
@ -1109,7 +1109,10 @@ dir = "21_macros"
test = false test = false
hint = """ hint = """
In order to use a macro outside of its module, you need to do something In order to use a macro outside of its module, you need to do something
special to the module to lift the macro out into its parent.""" special to the module to lift the macro out into its parent.
The same trick also works on "extern crate" statements for crates that have
exported macros, if you've seen any of those around."""
[[exercises]] [[exercises]]
name = "macros4" name = "macros4"
@ -1134,7 +1137,7 @@ dir = "22_clippy"
test = false test = false
strict_clippy = true strict_clippy = true
hint = """ hint = """
Rust stores the highest precision version of some long or infinite precision Rust stores the highest precision version of any long or infinite precision
mathematical constants in the Rust standard library: mathematical constants in the Rust standard library:
https://doc.rust-lang.org/stable/std/f32/consts/index.html https://doc.rust-lang.org/stable/std/f32/consts/index.html
@ -1142,7 +1145,7 @@ We may be tempted to use our own approximations for certain mathematical
constants, but clippy recognizes those imprecise mathematical constants as a constants, but clippy recognizes those imprecise mathematical constants as a
source of potential error. source of potential error.
See the suggestions of the Clippy warning in the compile output and use the See the suggestions of the clippy warning in compile output and use the
appropriate replacement constant from `std::f32::consts`...""" appropriate replacement constant from `std::f32::consts`..."""
[[exercises]] [[exercises]]
@ -1151,8 +1154,7 @@ dir = "22_clippy"
test = false test = false
strict_clippy = true strict_clippy = true
hint = """ hint = """
`for` loops over `Option` values are more clearly expressed as an `if-let` `for` loops over `Option` values are more clearly expressed as an `if let`"""
statement."""
[[exercises]] [[exercises]]
name = "clippy3" name = "clippy3"

View file

@ -1,37 +1 @@
// This program spawns multiple threads that each run for at least 250ms, and // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// each thread returns how much time they took to complete. The program should
// wait until all the spawned threads have finished and should collect their
// return values into a vector.
use std::{
thread,
time::{Duration, Instant},
};
fn main() {
let mut handles = Vec::new();
for i in 0..10 {
let handle = thread::spawn(move || {
let start = Instant::now();
thread::sleep(Duration::from_millis(250));
println!("Thread {i} done");
start.elapsed().as_millis()
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
// Collect the results of all threads into the `results` vector.
results.push(handle.join().unwrap());
}
if results.len() != 10 {
panic!("Oh no! Some thread isn't done yet!");
}
println!();
for (i, result) in results.into_iter().enumerate() {
println!("Thread {i} took {result}ms");
}
}

View file

@ -1,41 +1 @@
// Building on the last exercise, we want all of the threads to complete their // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// work. But this time, the spawned threads need to be in charge of updating a
// shared value: `JobStatus.jobs_done`
use std::{
sync::{Arc, Mutex},
thread,
time::Duration,
};
struct JobStatus {
jobs_done: u32,
}
fn main() {
// `Arc` isn't enough if you want a **mutable** shared state.
// We need to wrap the value with a `Mutex`.
let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 }));
// ^^^^^^^^^^^ ^
let mut handles = Vec::new();
for _ in 0..10 {
let status_shared = Arc::clone(&status);
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(250));
// Lock before you update a shared value.
status_shared.lock().unwrap().jobs_done += 1;
// ^^^^^^^^^^^^^^^^
});
handles.push(handle);
}
// Waiting for all jobs to complete.
for handle in handles {
handle.join().unwrap();
}
println!("Jobs done: {}", status.lock().unwrap().jobs_done);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

View file

@ -1,66 +1 @@
use std::{sync::mpsc, thread, time::Duration}; // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
struct Queue {
length: u32,
first_half: Vec<u32>,
second_half: Vec<u32>,
}
impl Queue {
fn new() -> Self {
Self {
length: 10,
first_half: vec![1, 2, 3, 4, 5],
second_half: vec![6, 7, 8, 9, 10],
}
}
}
fn send_tx(q: Queue, tx: mpsc::Sender<u32>) {
// Clone the sender `tx` first.
let tx_clone = tx.clone();
thread::spawn(move || {
for val in q.first_half {
println!("Sending {val:?}");
// Then use the clone in the first thread. This means that
// `tx_clone` is moved to the first thread and `tx` to the second.
tx_clone.send(val).unwrap();
thread::sleep(Duration::from_millis(250));
}
});
thread::spawn(move || {
for val in q.second_half {
println!("Sending {val:?}");
tx.send(val).unwrap();
thread::sleep(Duration::from_millis(250));
}
});
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn threads3() {
let (tx, rx) = mpsc::channel();
let queue = Queue::new();
let queue_length = queue.length;
send_tx(queue, tx);
let mut total_received: u32 = 0;
for received in rx {
println!("Got: {received}");
total_received += 1;
}
println!("Number of received values: {total_received}");
assert_eq!(total_received, queue_length);
}
}

View file

@ -1,10 +1 @@
macro_rules! my_macro { // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro!();
// ^
}

View file

@ -1,10 +1 @@
// Moved the macro definition to be before its call. // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro!();
}

View file

@ -1,13 +1 @@
// Added the attribute `macro_use` attribute. // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
#[macro_use]
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
fn main() {
my_macro!();
}

View file

@ -1,15 +1 @@
// Added semicolons to separate the macro arms. // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
#[rustfmt::skip]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
($val:expr) => {
println!("Look at this other macro: {}", $val);
};
}
fn main() {
my_macro!();
my_macro!(7777);
}

View file

@ -1,17 +1 @@
// The Clippy tool is a collection of lints to analyze your code so you can // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// catch common mistakes and improve your Rust code.
//
// 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.
use std::f32::consts::PI;
fn main() {
// Use the more accurate `PI` constant.
let pi = PI;
let radius: f32 = 5.0;
let area = pi * radius.powi(2);
println!("The area of a circle with radius {radius:.2} is {area:.5}");
}

View file

@ -1,10 +1 @@
fn main() { // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
let mut res = 42;
let option = Some(12);
// Use `if-let` instead of iteration.
if let Some(x) = option {
res += x;
}
println!("{res}");
}

View file

@ -1,31 +1 @@
use std::mem; // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
#[rustfmt::skip]
#[allow(unused_variables, unused_assignments)]
fn main() {
let my_option: Option<()> = None;
// `unwrap` of an `Option` after checking if it is `None` will panic.
// Use `if-let` instead.
if let Some(value) = my_option {
println!("{value:?}");
}
// A comma was missing.
let my_arr = &[
-1, -2, -3,
-4, -5, -6,
];
println!("My array! Here it is: {:?}", my_arr);
let mut my_empty_vec = vec![1, 2, 3, 4, 5];
// `resize` mutates a vector instead of returning a new one.
// `resize(0, …)` clears a vector, so it is better to use `clear`.
my_empty_vec.clear();
println!("This Vec is empty, see? {my_empty_vec:?}");
let mut value_a = 45;
let mut value_b = 66;
// Use `mem::swap` to correctly swap two values.
mem::swap(&mut value_a, &mut value_b);
println!("value a: {}; value b: {}", value_a, value_b);
}