diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2085b91..b9826cf0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## Unreleased
+
+### Changed
+
+- Upgrade to Rust edition 2024
+- Raise the minimum supported Rust version to `1.85`
+
## 6.4.0 (2024-11-11)
diff --git a/Cargo.toml b/Cargo.toml
index c229a3fd..e9b29eca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,8 +15,8 @@ authors = [
]
repository = "https://github.com/rust-lang/rustlings"
license = "MIT"
-edition = "2021" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
-rust-version = "1.80"
+edition = "2024" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
+rust-version = "1.85"
[workspace.dependencies]
serde = { version = "1.0.217", features = ["derive"] }
diff --git a/dev/Cargo.toml b/dev/Cargo.toml
index 29a557a0..ae380d17 100644
--- a/dev/Cargo.toml
+++ b/dev/Cargo.toml
@@ -192,7 +192,7 @@ bin = [
[package]
name = "exercises"
-edition = "2021"
+edition = "2024"
# Don't publish the exercises on crates.io!
publish = false
diff --git a/src/app_state.rs b/src/app_state.rs
index 5979150f..d1c45d49 100644
--- a/src/app_state.rs
+++ b/src/app_state.rs
@@ -1,11 +1,11 @@
-use anyhow::{bail, Context, Error, Result};
-use crossterm::{cursor, terminal, QueueableCommand};
+use anyhow::{Context, Error, Result, bail};
+use crossterm::{QueueableCommand, cursor, terminal};
use std::{
collections::HashSet,
env,
fs::{File, OpenOptions},
io::{Read, Seek, StdoutLock, Write},
- path::{Path, MAIN_SEPARATOR_STR},
+ path::{MAIN_SEPARATOR_STR, Path},
process::{Command, Stdio},
sync::{
atomic::{AtomicUsize, Ordering::Relaxed},
@@ -427,32 +427,34 @@ impl AppState {
let next_exercise_ind = &next_exercise_ind;
let slf = &self;
thread::Builder::new()
- .spawn_scoped(s, move || loop {
- let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
- let Some(exercise) = slf.exercises.get(exercise_ind) else {
- // No more exercises.
- break;
- };
+ .spawn_scoped(s, move || {
+ loop {
+ let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
+ let Some(exercise) = slf.exercises.get(exercise_ind) else {
+ // No more exercises.
+ break;
+ };
- if exercise_progress_sender
- .send((exercise_ind, CheckProgress::Checking))
- .is_err()
- {
- break;
- };
+ if exercise_progress_sender
+ .send((exercise_ind, CheckProgress::Checking))
+ .is_err()
+ {
+ break;
+ };
- let success = exercise.run_exercise(None, &slf.cmd_runner);
- let progress = match success {
- Ok(true) => CheckProgress::Done,
- Ok(false) => CheckProgress::Pending,
- Err(_) => CheckProgress::None,
- };
+ let success = exercise.run_exercise(None, &slf.cmd_runner);
+ let progress = match success {
+ Ok(true) => CheckProgress::Done,
+ Ok(false) => CheckProgress::Pending,
+ Err(_) => CheckProgress::None,
+ };
- if exercise_progress_sender
- .send((exercise_ind, progress))
- .is_err()
- {
- break;
+ if exercise_progress_sender
+ .send((exercise_ind, progress))
+ .is_err()
+ {
+ break;
+ }
}
})
.context("Failed to spawn a thread to check all exercises")?;
diff --git a/src/cmd.rs b/src/cmd.rs
index 30f988a6..551df8f0 100644
--- a/src/cmd.rs
+++ b/src/cmd.rs
@@ -1,4 +1,4 @@
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use serde::Deserialize;
use std::{
io::Read,
diff --git a/src/dev.rs b/src/dev.rs
index 8af40d69..354d77c4 100644
--- a/src/dev.rs
+++ b/src/dev.rs
@@ -1,4 +1,4 @@
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use clap::Subcommand;
use std::path::PathBuf;
diff --git a/src/dev/check.rs b/src/dev/check.rs
index 956c2be2..aacc2f44 100644
--- a/src/dev/check.rs
+++ b/src/dev/check.rs
@@ -1,8 +1,8 @@
-use anyhow::{anyhow, bail, Context, Error, Result};
+use anyhow::{Context, Error, Result, anyhow, bail};
use std::{
cmp::Ordering,
collections::HashSet,
- fs::{self, read_dir, OpenOptions},
+ fs::{self, OpenOptions, read_dir},
io::{self, Read, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
@@ -10,11 +10,11 @@ use std::{
};
use crate::{
- cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
- cmd::CmdRunner,
- exercise::{RunnableExercise, OUTPUT_CAPACITY},
- info_file::{ExerciseInfo, InfoFile},
CURRENT_FORMAT_VERSION,
+ cargo_toml::{BINS_BUFFER_CAPACITY, append_bins, bins_start_end_ind},
+ cmd::CmdRunner,
+ exercise::{OUTPUT_CAPACITY, RunnableExercise},
+ info_file::{ExerciseInfo, InfoFile},
};
const MAX_N_EXERCISES: usize = 999;
@@ -42,10 +42,14 @@ fn check_cargo_toml(
if old_bins != new_bins {
if cfg!(debug_assertions) {
- bail!("The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again");
+ bail!(
+ "The file `dev/Cargo.toml` is outdated. Run `cargo run -- dev update` to update it. Then run `cargo run -- dev check` again"
+ );
}
- bail!("The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again");
+ bail!(
+ "The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again"
+ );
}
Ok(())
@@ -63,7 +67,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> {
bail!("Found an empty exercise name in `info.toml`");
}
if name.len() > MAX_EXERCISE_NAME_LEN {
- bail!("The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}");
+ bail!(
+ "The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}"
+ );
}
if let Some(c) = forbidden_char(name) {
bail!("Char `{c}` in the exercise name `{name}` is not allowed");
@@ -79,7 +85,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> {
}
if exercise_info.hint.trim_ascii().is_empty() {
- bail!("The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise");
+ bail!(
+ "The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise"
+ );
}
if !names.insert(name) {
@@ -96,20 +104,28 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> {
.with_context(|| format!("Failed to read the file {path}"))?;
if !file_buf.contains("fn main()") {
- 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.");
+ bail!(
+ "Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user."
+ );
}
let contains_tests = file_buf.contains("#[test]\n");
if exercise_info.test {
if !contains_tests {
- bail!("The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file");
+ bail!(
+ "The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file"
+ );
}
} else if contains_tests {
- 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"
+ );
}
file_buf.clear();
@@ -125,7 +141,10 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> {
// Only one level of directory nesting is allowed.
fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet) -> Result<()> {
let unexpected_file = |path: &Path| {
- anyhow!("Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory", path.display())
+ anyhow!(
+ "Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory",
+ path.display()
+ )
};
for entry in read_dir(dir).with_context(|| format!("Failed to open the `{dir}` directory"))? {
@@ -154,7 +173,10 @@ fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet) -> R
let path = entry.path();
if !entry.file_type().unwrap().is_file() {
- bail!("Found `{}` but expected only files. Only one level of exercise nesting is allowed", path.display());
+ bail!(
+ "Found `{}` but expected only files. Only one level of exercise nesting is allowed",
+ path.display()
+ );
}
let file_name = path.file_name().unwrap();
@@ -224,8 +246,12 @@ fn check_exercises_unsolved(
fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> {
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::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"),
+ 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::Equal => (),
}
diff --git a/src/dev/new.rs b/src/dev/new.rs
index 154cd224..ba3517f5 100644
--- a/src/dev/new.rs
+++ b/src/dev/new.rs
@@ -1,4 +1,4 @@
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use std::{
env::set_current_dir,
fs::{self, create_dir},
@@ -6,7 +6,7 @@ use std::{
process::Command,
};
-use crate::{init::RUST_ANALYZER_TOML, CURRENT_FORMAT_VERSION};
+use crate::{CURRENT_FORMAT_VERSION, init::RUST_ANALYZER_TOML};
// Create a directory relative to the current directory and print its path.
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
@@ -55,7 +55,9 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
write_rel_file(
"info.toml",
&dir_path_str,
- format!("{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"),
+ format!(
+ "{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"
+ ),
)?;
write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?;
@@ -130,7 +132,7 @@ bin = []
[package]
name = "exercises"
-edition = "2021"
+edition = "2024"
# Don't publish the exercises on crates.io!
publish = false
diff --git a/src/exercise.rs b/src/exercise.rs
index 84908284..fdfbc4f6 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -1,13 +1,13 @@
use anyhow::Result;
use crossterm::{
- style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
QueueableCommand,
+ style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
};
use std::io::{self, StdoutLock, Write};
use crate::{
cmd::CmdRunner,
- term::{self, terminal_file_link, write_ansi, CountedWrite},
+ term::{self, CountedWrite, terminal_file_link, write_ansi},
};
/// The initial capacity of the output buffer.
diff --git a/src/info_file.rs b/src/info_file.rs
index fdc8f0f3..ec61f8ad 100644
--- a/src/info_file.rs
+++ b/src/info_file.rs
@@ -1,4 +1,4 @@
-use anyhow::{bail, Context, Error, Result};
+use anyhow::{Context, Error, Result, bail};
use serde::Deserialize;
use std::{fs, io::ErrorKind};
diff --git a/src/init.rs b/src/init.rs
index ce49bb65..208425c2 100644
--- a/src/init.rs
+++ b/src/init.rs
@@ -1,7 +1,7 @@
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use crossterm::{
- style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
QueueableCommand,
+ style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
};
use serde::Deserialize;
use std::{
@@ -57,7 +57,9 @@ pub fn init() -> Result<()> {
if !workspace_manifest_content.contains("[workspace]\n")
&& !workspace_manifest_content.contains("workspace.")
{
- bail!("The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory");
+ bail!(
+ "The current directory is already part of a Cargo project.\nPlease initialize Rustlings in a different directory"
+ );
}
stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\nPress ENTER to continue ")?;
@@ -75,7 +77,9 @@ pub fn init() -> Result<()> {
.stdout(Stdio::null())
.status()?;
if !status.success() {
- bail!("Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory");
+ bail!(
+ "Failed to initialize a new Cargo workspace member.\nPlease initialize Rustlings in a different directory"
+ );
}
stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?;
diff --git a/src/list.rs b/src/list.rs
index 9f243a17..a2eee9e1 100644
--- a/src/list.rs
+++ b/src/list.rs
@@ -1,14 +1,13 @@
use anyhow::{Context, Result};
use crossterm::{
- cursor,
+ QueueableCommand, cursor,
event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, MouseEventKind,
},
terminal::{
- disable_raw_mode, enable_raw_mode, DisableLineWrap, EnableLineWrap, EnterAlternateScreen,
- LeaveAlternateScreen,
+ DisableLineWrap, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen,
+ disable_raw_mode, enable_raw_mode,
},
- QueueableCommand,
};
use std::io::{self, StdoutLock, Write};
diff --git a/src/list/state.rs b/src/list/state.rs
index 0670fa46..ae65ec2b 100644
--- a/src/list/state.rs
+++ b/src/list/state.rs
@@ -1,11 +1,11 @@
use anyhow::{Context, Result};
use crossterm::{
+ QueueableCommand,
cursor::{MoveTo, MoveToNextLine},
style::{
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
},
terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate},
- QueueableCommand,
};
use std::{
fmt::Write as _,
@@ -15,7 +15,7 @@ use std::{
use crate::{
app_state::AppState,
exercise::Exercise,
- term::{progress_bar, CountedWrite, MaxLenWriter},
+ term::{CountedWrite, MaxLenWriter, progress_bar},
};
use super::scroll_state::ScrollState;
diff --git a/src/main.rs b/src/main.rs
index eeb1883e..6688e3e6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use app_state::StateFileStatus;
use clap::{Parser, Subcommand};
use std::{
diff --git a/src/run.rs b/src/run.rs
index ac8b26ad..6f4f099b 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -1,7 +1,7 @@
use anyhow::Result;
use crossterm::{
- style::{Color, ResetColor, SetForegroundColor},
QueueableCommand,
+ style::{Color, ResetColor, SetForegroundColor},
};
use std::{
io::{self, Write},
@@ -10,7 +10,7 @@ use std::{
use crate::{
app_state::{AppState, ExercisesProgress},
- exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
+ exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
};
pub fn run(app_state: &mut AppState) -> Result {
diff --git a/src/term.rs b/src/term.rs
index cb0a07ce..1e08c84f 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -1,8 +1,8 @@
use crossterm::{
+ Command, QueueableCommand,
cursor::MoveTo,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
terminal::{Clear, ClearType},
- Command, QueueableCommand,
};
use std::{
fmt, fs,
diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs
index 2051e544..9c05f10d 100644
--- a/src/watch/notify_event.rs
+++ b/src/watch/notify_event.rs
@@ -1,18 +1,18 @@
use anyhow::{Context, Result};
use notify::{
- event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
Event, EventKind,
+ event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
};
use std::{
sync::{
atomic::Ordering::Relaxed,
- mpsc::{sync_channel, RecvTimeoutError, Sender, SyncSender},
+ mpsc::{RecvTimeoutError, Sender, SyncSender, sync_channel},
},
thread,
time::Duration,
};
-use super::{WatchEvent, EXERCISE_RUNNING};
+use super::{EXERCISE_RUNNING, WatchEvent};
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
diff --git a/src/watch/state.rs b/src/watch/state.rs
index 5263bc57..2413becd 100644
--- a/src/watch/state.rs
+++ b/src/watch/state.rs
@@ -1,24 +1,25 @@
use anyhow::{Context, Result};
use crossterm::{
+ QueueableCommand,
style::{
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
},
- terminal, QueueableCommand,
+ terminal,
};
use std::{
io::{self, Read, StdoutLock, Write},
- sync::mpsc::{sync_channel, Sender, SyncSender},
+ sync::mpsc::{Sender, SyncSender, sync_channel},
thread,
};
use crate::{
app_state::{AppState, ExercisesProgress},
clear_terminal,
- exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY},
+ exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
term::progress_bar,
};
-use super::{terminal_event::terminal_event_handler, InputPauseGuard, WatchEvent};
+use super::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler};
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
.with(Attribute::Bold)
diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs
index 48411db0..2400a3df 100644
--- a/src/watch/terminal_event.rs
+++ b/src/watch/terminal_event.rs
@@ -4,7 +4,7 @@ use std::sync::{
mpsc::{Receiver, Sender},
};
-use super::{WatchEvent, EXERCISE_RUNNING};
+use super::{EXERCISE_RUNNING, WatchEvent};
pub enum InputEvent {
Next,
diff --git a/tests/test_exercises/dev/Cargo.toml b/tests/test_exercises/dev/Cargo.toml
index 01fe7c10..74dcc20a 100644
--- a/tests/test_exercises/dev/Cargo.toml
+++ b/tests/test_exercises/dev/Cargo.toml
@@ -7,5 +7,5 @@ bin = [
[package]
name = "test_exercises"
-edition = "2021"
+edition = "2024"
publish = false