Compare commits

...

9 commits

Author SHA1 Message Date
Enrico 60d686f00e
Merge e59c65cf45 into 930a0ea73b 2024-10-18 10:42:35 -06:00
mo8it 930a0ea73b list: Highlight search match in exercise names 2024-10-17 16:00:10 +02:00
mo8it 7e2f56f41a Use the default hasher 2024-10-17 15:03:43 +02:00
mo8it e90f5f03f3 Mention the Q&A category 2024-10-17 14:59:37 +02:00
mo8it 0e090ae112 Add required type annotation 2024-10-17 14:49:07 +02:00
mo8it 99496706c5 Apply new Clippy lints 2024-10-17 14:49:07 +02:00
mo8it f146553dea hashmap3: Use or_default 2024-10-17 14:49:07 +02:00
Enrico e59c65cf45 chore: Refactored to adhere to new rustlings version 2024-09-29 20:58:33 +02:00
Enrico 1c27aeead9 feat: add functions6.rs and move_semantics6.rs exercises about closures 2024-09-28 11:59:09 +02:00
21 changed files with 161 additions and 66 deletions

15
Cargo.lock generated
View file

@ -186,12 +186,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "foldhash"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
[[package]] [[package]]
name = "fsevent-sys" name = "fsevent-sys"
version = "4.1.0" version = "4.1.0"
@ -283,9 +277,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.159" version = "0.2.160"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f"
[[package]] [[package]]
name = "libredox" name = "libredox"
@ -410,9 +404,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.87" version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -455,7 +449,6 @@ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"crossterm", "crossterm",
"foldhash",
"notify", "notify",
"os_pipe", "os_pipe",
"rustix", "rustix",

View file

@ -49,7 +49,6 @@ include = [
anyhow = "1.0.89" anyhow = "1.0.89"
clap = { version = "4.5.20", features = ["derive"] } clap = { version = "4.5.20", features = ["derive"] }
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] } crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
foldhash = "0.1.3"
notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] } notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] }
os_pipe = "1.2.1" os_pipe = "1.2.1"
rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" } rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" }

View file

@ -124,14 +124,13 @@ The list allows you to…
- See the status of all exercises (done or pending) - See the status of all exercises (done or pending)
- `c`: Continue at another exercise (temporarily skip some exercises or go back to a previous one) - `c`: Continue at another exercise (temporarily skip some exercises or go back to a previous one)
- `r`: Reset status and file of an exercise (you need to _reload/reopen_ its file in your editor afterwards) - `r`: Reset status and file of the selected exercise (you need to _reload/reopen_ its file in your editor afterwards)
See the footer of the list for all possible keys. See the footer of the list for all possible keys.
## Continuing On ## Questions?
Once you've completed Rustlings, put your new knowledge to good use! If you need any help while doing the exercises and the builtin-hints aren't helpful, feel free to ask in the [_Q&A_ category of the discussions](https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q=) if your question wasn't asked yet 💡
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
## Third-Party Exercises ## Third-Party Exercises
@ -144,6 +143,11 @@ Do you want to create your own set of Rustlings exercises to focus on some speci
Or do you want to translate the original Rustlings exercises? Or do you want to translate the original Rustlings exercises?
Then follow the the guide about [third-party exercises](https://github.com/rust-lang/rustlings/blob/main/THIRD_PARTY_EXERCISES.md)! Then follow the the guide about [third-party exercises](https://github.com/rust-lang/rustlings/blob/main/THIRD_PARTY_EXERCISES.md)!
## Continuing On
Once you've completed Rustlings, put your new knowledge to good use!
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
## Uninstalling Rustlings ## Uninstalling Rustlings
If you want to remove Rustlings from your system, run the following command: If you want to remove Rustlings from your system, run the following command:

View file

@ -5,9 +5,6 @@ disallowed-types = [
] ]
disallowed-methods = [ disallowed-methods = [
# We use `foldhash` instead of the default hasher.
"std::collections::HashSet::new",
"std::collections::HashSet::with_capacity",
# Inefficient. Use `.queue(…)` instead. # Inefficient. Use `.queue(…)` instead.
"crossterm::style::style", "crossterm::style::style",
# Use `thread::Builder::spawn` instead and handle the error. # Use `thread::Builder::spawn` instead and handle the error.

View file

@ -26,6 +26,8 @@ bin = [
{ name = "functions4_sol", path = "../solutions/02_functions/functions4.rs" }, { name = "functions4_sol", path = "../solutions/02_functions/functions4.rs" },
{ name = "functions5", path = "../exercises/02_functions/functions5.rs" }, { name = "functions5", path = "../exercises/02_functions/functions5.rs" },
{ name = "functions5_sol", path = "../solutions/02_functions/functions5.rs" }, { name = "functions5_sol", path = "../solutions/02_functions/functions5.rs" },
{ name = "functions6", path = "../exercises/02_functions/functions6.rs" },
{ name = "functions6_sol", path = "../solutions/02_functions/functions6.rs" },
{ name = "if1", path = "../exercises/03_if/if1.rs" }, { name = "if1", path = "../exercises/03_if/if1.rs" },
{ name = "if1_sol", path = "../solutions/03_if/if1.rs" }, { name = "if1_sol", path = "../solutions/03_if/if1.rs" },
{ name = "if2", path = "../exercises/03_if/if2.rs" }, { name = "if2", path = "../exercises/03_if/if2.rs" },
@ -60,6 +62,8 @@ bin = [
{ name = "move_semantics4_sol", path = "../solutions/06_move_semantics/move_semantics4.rs" }, { name = "move_semantics4_sol", path = "../solutions/06_move_semantics/move_semantics4.rs" },
{ name = "move_semantics5", path = "../exercises/06_move_semantics/move_semantics5.rs" }, { name = "move_semantics5", path = "../exercises/06_move_semantics/move_semantics5.rs" },
{ name = "move_semantics5_sol", path = "../solutions/06_move_semantics/move_semantics5.rs" }, { name = "move_semantics5_sol", path = "../solutions/06_move_semantics/move_semantics5.rs" },
{ name = "move_semantics6", path = "../exercises/06_move_semantics/move_semantics6.rs" },
{ name = "move_semantics6_sol", path = "../solutions/06_move_semantics/move_semantics6.rs" },
{ name = "structs1", path = "../exercises/07_structs/structs1.rs" }, { name = "structs1", path = "../exercises/07_structs/structs1.rs" },
{ name = "structs1_sol", path = "../solutions/07_structs/structs1.rs" }, { name = "structs1_sol", path = "../solutions/07_structs/structs1.rs" },
{ name = "structs2", path = "../exercises/07_structs/structs2.rs" }, { name = "structs2", path = "../exercises/07_structs/structs2.rs" },

View file

@ -1,8 +1,9 @@
# Functions # Functions
Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even
in more complex code. in more complex code. You will also learn what is the difference with closures.
## Further information ## Further information
- [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html) - [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html)
- [Closures](https://doc.rust-lang.org/book/ch13-01-closures.html)

View file

@ -0,0 +1,19 @@
// functions6.rs
//
// Here you can practice special functions called `closures`, that can capture
// variables of their parent context.
// Fix the code below to make it compile, without changing the two closure
// definitions.
//
// Execute `rustlings hint functions6` or use the `hint` watch subcommand for
// some hints.
fn main() {
// TODO: ensure the definition of captured variable
let closure_1 = |input_var: u32| -> u32 {input_var + outer_var};
println!("Closure#1 returns {}", closure_1(5));
let closure_2 = |input_var| println!("Closure#2 (input_var {})", input_var);
closure_2(2);
closure_2("5"); // TODO: look at the captured variable type here
}

View file

@ -0,0 +1,25 @@
// move_semantics6.rs
//
// Here you will practice how mutable/immutable borrowing works in the context
// of a closure.
//
// Try to fix this code to make it compile and not panic.
// You can't change anything except removing 1 line.
//
// Execute `rustlings hint move_semantics7` or use the `hint` watch subcommand
// for a hint.
fn main() {
let mut counter = 0;
let mut increment = || {
counter += 1;
println!("counter equals {}", counter);
};
increment();
let _reborrowed_counter = &counter; // TODO: figure out where to put this borrowing instruction
increment();
assert_eq!(counter, 2);
}

View file

@ -17,7 +17,7 @@ struct TeamScores {
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> { fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
// The name of the team is the key and its associated struct is the value. // The name of the team is the key and its associated struct is the value.
let mut scores = HashMap::new(); let mut scores = HashMap::<&str, TeamScores>::new();
for line in results.lines() { for line in results.lines() {
let mut split_iterator = line.split(','); let mut split_iterator = line.split(',');

View file

@ -3,7 +3,7 @@
| Exercise | Book Chapter | | Exercise | Book Chapter |
| ---------------------- | ------------------- | | ---------------------- | ------------------- |
| variables | §3.1 | | variables | §3.1 |
| functions | §3.3 | | functions | §3.3, §13.1 |
| if | §3.5 | | if | §3.5 |
| primitive_types | §3.2, §4.3 | | primitive_types | §3.2, §4.3 |
| vecs | §8.1 | | vecs | §8.1 |

View file

@ -187,6 +187,20 @@ There are two solutions:
1. Add the `return` keyword before `num * num;` 1. Add the `return` keyword before `num * num;`
2. Remove the semicolon `;` after `num * num`""" 2. Remove the semicolon `;` after `num * num`"""
[[exercises]]
name = "functions6"
dir = "02_functions"
test = false
hint = """
Hint FIX #1: Closures can capture variables defined in the outer context.
Hint FIX #2: Closures can infer both input and returned types, when they are not
specified in the signature. But the closure cannot be reused with different
input types.
Read more about closures in the rust book dedicated section:
https://doc.rust-lang.org/book/ch13-01-closures.html"""
# IF # IF
[[exercises]] [[exercises]]
@ -391,6 +405,18 @@ The first problem is that `get_char` is taking ownership of the string. So
Once you've fixed that, `string_uppercase`'s function signature will also need Once you've fixed that, `string_uppercase`'s function signature will also need
to be adjusted.""" to be adjusted."""
[[exercises]]
name = "move_semantics6"
dir = "06_move_semantics"
test = false
hint = """
When a closure captures a variable to modify it, it actually borrows that variable
as a mutable reference. In this exercise, the closure mutably borrows the `counter`
variable, thus, any attempt to borrow `counter` between closure calls leads to an error.
You cannot immutably borrow a variable if a mutable closure is
called later in the scope."""
# STRUCTS # STRUCTS
[[exercises]] [[exercises]]
@ -575,12 +601,8 @@ https://doc.rust-lang.org/book/ch08-03-hash-maps.html#only-inserting-a-value-if-
name = "hashmaps3" name = "hashmaps3"
dir = "11_hashmaps" dir = "11_hashmaps"
hint = """ hint = """
Hint 1: Use the `entry()` and `or_insert()` (or `or_insert_with()`) methods of Hint 1: Use the `entry()` and `or_default()` methods of `HashMap` to insert the
`HashMap` to insert the default value of `TeamScores` if a team doesn't default value of `TeamScores` if a team doesn't exist in the table yet.
exist in the table yet.
Learn more in The Book:
https://doc.rust-lang.org/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value
Hint 2: If there is already an entry for a given key, the value returned by Hint 2: If there is already an entry for a given key, the value returned by
`entry()` can be updated based on the existing value. `entry()` can be updated based on the existing value.

View file

@ -0,0 +1,9 @@
fn main() {
let outer_var = 1;
let closure_1 = |input_var: u32| -> u32 { input_var + outer_var };
println!("Closure#1 returns {}", closure_1(5));
let closure_2 = |input_var| println!("Closure#2 (input_var {})", input_var);
closure_2(2);
closure_2(5);
}

View file

@ -0,0 +1,14 @@
fn main() {
let mut counter = 0;
let mut increment = || {
counter += 1;
println!("counter equals {}", counter);
};
increment();
increment();
let _reborrowed_counter = &counter;
assert_eq!(counter, 2);
}

View file

@ -17,7 +17,7 @@ struct TeamScores {
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> { fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
// The name of the team is the key and its associated struct is the value. // The name of the team is the key and its associated struct is the value.
let mut scores = HashMap::new(); let mut scores = HashMap::<&str, TeamScores>::new();
for line in results.lines() { for line in results.lines() {
let mut split_iterator = line.split(','); let mut split_iterator = line.split(',');
@ -28,17 +28,13 @@ fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
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 team_1 = scores let team_1 = scores.entry(team_1_name).or_default();
.entry(team_1_name)
.or_insert_with(TeamScores::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;
// Similarly for the second team. // Similarly for the second team.
let team_2 = scores let team_2 = scores.entry(team_2_name).or_default();
.entry(team_2_name)
.or_insert_with(TeamScores::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

@ -1,6 +1,7 @@
use anyhow::{bail, Context, Error, Result}; use anyhow::{bail, Context, Error, Result};
use crossterm::{cursor, terminal, QueueableCommand}; use crossterm::{cursor, terminal, QueueableCommand};
use std::{ use std::{
collections::HashSet,
env, env,
fs::{File, OpenOptions}, fs::{File, OpenOptions},
io::{Read, Seek, StdoutLock, Write}, io::{Read, Seek, StdoutLock, Write},
@ -16,7 +17,6 @@ use std::{
use crate::{ use crate::{
clear_terminal, clear_terminal,
cmd::CmdRunner, cmd::CmdRunner,
collections::hash_set_with_capacity,
embedded::EMBEDDED_FILES, embedded::EMBEDDED_FILES,
exercise::{Exercise, RunnableExercise}, exercise::{Exercise, RunnableExercise},
info_file::ExerciseInfo, info_file::ExerciseInfo,
@ -146,7 +146,7 @@ impl AppState {
break 'block StateFileStatus::NotRead; break 'block StateFileStatus::NotRead;
} }
let mut done_exercises = hash_set_with_capacity(exercises.len()); let mut done_exercises = HashSet::with_capacity(exercises.len());
for done_exercise_name in lines { for done_exercise_name in lines {
if done_exercise_name.is_empty() { if done_exercise_name.is_empty() {

View file

@ -125,7 +125,7 @@ pub struct CargoSubcommand<'out> {
output: Option<&'out mut Vec<u8>>, output: Option<&'out mut Vec<u8>>,
} }
impl<'out> CargoSubcommand<'out> { impl CargoSubcommand<'_> {
#[inline] #[inline]
pub fn args<'arg, I>(&mut self, args: I) -> &mut Self pub fn args<'arg, I>(&mut self, args: I) -> &mut Self
where where

View file

@ -1,9 +0,0 @@
use foldhash::fast::FixedState;
/// DOS attacks aren't a concern for Rustlings. Therefore, we use `foldhash` with a fixed state.
pub type HashSet<T> = std::collections::HashSet<T, FixedState>;
#[inline]
pub fn hash_set_with_capacity<T>(capacity: usize) -> HashSet<T> {
HashSet::with_capacity_and_hasher(capacity, FixedState::default())
}

View file

@ -1,6 +1,7 @@
use anyhow::{anyhow, bail, Context, Error, Result}; use anyhow::{anyhow, bail, Context, Error, Result};
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashSet,
fs::{self, read_dir, OpenOptions}, fs::{self, read_dir, OpenOptions},
io::{self, Read, Write}, io::{self, Read, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -11,7 +12,6 @@ use std::{
use crate::{ use crate::{
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY}, cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
cmd::CmdRunner, cmd::CmdRunner,
collections::{hash_set_with_capacity, HashSet},
exercise::{RunnableExercise, OUTPUT_CAPACITY}, exercise::{RunnableExercise, OUTPUT_CAPACITY},
info_file::{ExerciseInfo, InfoFile}, info_file::{ExerciseInfo, InfoFile},
CURRENT_FORMAT_VERSION, CURRENT_FORMAT_VERSION,
@ -53,8 +53,8 @@ fn check_cargo_toml(
// Check the info of all exercises and return their paths in a set. // Check the info of all exercises and return their paths in a set.
fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> { fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
let mut names = hash_set_with_capacity(info_file.exercises.len()); let mut names = HashSet::with_capacity(info_file.exercises.len());
let mut paths = hash_set_with_capacity(info_file.exercises.len()); let mut paths = HashSet::with_capacity(info_file.exercises.len());
let mut file_buf = String::with_capacity(1 << 14); let mut file_buf = String::with_capacity(1 << 14);
for exercise_info in &info_file.exercises { for exercise_info in &info_file.exercises {
@ -282,7 +282,7 @@ fn check_solutions(
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.context("Failed to spawn a thread to check a solution")?; .context("Failed to spawn a thread to check a solution")?;
let mut sol_paths = hash_set_with_capacity(info_file.exercises.len()); let mut sol_paths = HashSet::with_capacity(info_file.exercises.len());
let mut fmt_cmd = Command::new("rustfmt"); let mut fmt_cmd = Command::new("rustfmt");
fmt_cmd fmt_cmd
.arg("--check") .arg("--check")

View file

@ -105,6 +105,28 @@ impl<'a> ListState<'a> {
); );
} }
fn draw_exericse_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> {
if !self.search_query.is_empty() {
if let Some((pre_highlight, highlight, post_highlight)) = exercise
.name
.find(&self.search_query)
.and_then(|ind| exercise.name.split_at_checked(ind))
.and_then(|(pre_highlight, rest)| {
rest.split_at_checked(self.search_query.len())
.map(|x| (pre_highlight, x.0, x.1))
})
{
writer.write_str(pre_highlight)?;
writer.stdout.queue(SetForegroundColor(Color::Magenta))?;
writer.write_str(highlight)?;
writer.stdout.queue(ResetColor)?;
return writer.write_str(post_highlight);
}
}
writer.write_str(exercise.name)
}
fn draw_rows( fn draw_rows(
&self, &self,
stdout: &mut StdoutLock, stdout: &mut StdoutLock,
@ -147,10 +169,10 @@ impl<'a> ListState<'a> {
writer.stdout.queue(SetForegroundColor(Color::Yellow))?; writer.stdout.queue(SetForegroundColor(Color::Yellow))?;
writer.write_ascii(b"PENDING ")?; writer.write_ascii(b"PENDING ")?;
} }
writer.stdout.queue(SetForegroundColor(Color::Reset))?; writer.stdout.queue(SetForegroundColor(Color::Reset))?;
writer.write_str(exercise.name)?; self.draw_exericse_name(&mut writer, exercise)?;
writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?; writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?;
// The list links aren't shown correctly in VS Code on Windows. // The list links aren't shown correctly in VS Code on Windows.

View file

@ -13,7 +13,6 @@ use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile};
mod app_state; mod app_state;
mod cargo_toml; mod cargo_toml;
mod cmd; mod cmd;
mod collections;
mod dev; mod dev;
mod embedded; mod embedded;
mod exercise; mod exercise;

View file

@ -11,15 +11,15 @@ use std::{
use crate::app_state::CheckProgress; use crate::app_state::CheckProgress;
pub struct MaxLenWriter<'a, 'b> { pub struct MaxLenWriter<'a, 'lock> {
pub stdout: &'a mut StdoutLock<'b>, pub stdout: &'a mut StdoutLock<'lock>,
len: usize, len: usize,
max_len: usize, max_len: usize,
} }
impl<'a, 'b> MaxLenWriter<'a, 'b> { impl<'a, 'lock> MaxLenWriter<'a, 'lock> {
#[inline] #[inline]
pub fn new(stdout: &'a mut StdoutLock<'b>, max_len: usize) -> Self { pub fn new(stdout: &'a mut StdoutLock<'lock>, max_len: usize) -> Self {
Self { Self {
stdout, stdout,
len: 0, len: 0,
@ -34,13 +34,13 @@ impl<'a, 'b> MaxLenWriter<'a, 'b> {
} }
} }
pub trait CountedWrite<'a> { pub trait CountedWrite<'lock> {
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()>; fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()>;
fn write_str(&mut self, unicode: &str) -> io::Result<()>; fn write_str(&mut self, unicode: &str) -> io::Result<()>;
fn stdout(&mut self) -> &mut StdoutLock<'a>; fn stdout(&mut self) -> &mut StdoutLock<'lock>;
} }
impl<'a, 'b> CountedWrite<'b> for MaxLenWriter<'a, 'b> { impl<'lock> CountedWrite<'lock> for MaxLenWriter<'_, 'lock> {
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> { fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> {
let n = ascii.len().min(self.max_len.saturating_sub(self.len)); let n = ascii.len().min(self.max_len.saturating_sub(self.len));
if n > 0 { if n > 0 {
@ -65,7 +65,7 @@ impl<'a, 'b> CountedWrite<'b> for MaxLenWriter<'a, 'b> {
} }
#[inline] #[inline]
fn stdout(&mut self) -> &mut StdoutLock<'b> { fn stdout(&mut self) -> &mut StdoutLock<'lock> {
self.stdout self.stdout
} }
} }
@ -87,17 +87,17 @@ impl<'a> CountedWrite<'a> for StdoutLock<'a> {
} }
} }
pub struct CheckProgressVisualizer<'a, 'b> { pub struct CheckProgressVisualizer<'a, 'lock> {
stdout: &'a mut StdoutLock<'b>, stdout: &'a mut StdoutLock<'lock>,
n_cols: usize, n_cols: usize,
} }
impl<'a, 'b> CheckProgressVisualizer<'a, 'b> { impl<'a, 'lock> CheckProgressVisualizer<'a, 'lock> {
const CHECKING_COLOR: Color = Color::Blue; const CHECKING_COLOR: Color = Color::Blue;
const DONE_COLOR: Color = Color::Green; const DONE_COLOR: Color = Color::Green;
const PENDING_COLOR: Color = Color::Red; const PENDING_COLOR: Color = Color::Red;
pub fn build(stdout: &'a mut StdoutLock<'b>, term_width: u16) -> io::Result<Self> { pub fn build(stdout: &'a mut StdoutLock<'lock>, term_width: u16) -> io::Result<Self> {
clear_terminal(stdout)?; clear_terminal(stdout)?;
stdout.write_all("Checking all exercises…\n".as_bytes())?; stdout.write_all("Checking all exercises…\n".as_bytes())?;