1
0
mirror of https://github.com/rust-lang/rustlings.git synced 2025-04-27 12:22:47 +02:00

Upgrade to edition 2024

This commit is contained in:
mo8it 2025-02-18 20:10:52 +01:00
parent 298be671b9
commit d9872f2615
20 changed files with 120 additions and 79 deletions

View File

@ -1,3 +1,10 @@
## Unreleased
### Changed
- Upgrade to Rust edition 2024
- Raise the minimum supported Rust version to `1.85`
<a name="6.4.0"></a> <a name="6.4.0"></a>
## 6.4.0 (2024-11-11) ## 6.4.0 (2024-11-11)

View File

@ -15,8 +15,8 @@ authors = [
] ]
repository = "https://github.com/rust-lang/rustlings" repository = "https://github.com/rust-lang/rustlings"
license = "MIT" license = "MIT"
edition = "2021" # On Update: Update the edition of the `rustfmt` command that checks the solutions. edition = "2024" # On Update: Update the edition of the `rustfmt` command that checks the solutions.
rust-version = "1.80" rust-version = "1.85"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1.0.217", features = ["derive"] } serde = { version = "1.0.217", features = ["derive"] }

View File

@ -192,7 +192,7 @@ bin = [
[package] [package]
name = "exercises" name = "exercises"
edition = "2021" edition = "2024"
# Don't publish the exercises on crates.io! # Don't publish the exercises on crates.io!
publish = false publish = false

View File

@ -1,11 +1,11 @@
use anyhow::{bail, Context, Error, Result}; use anyhow::{Context, Error, Result, bail};
use crossterm::{cursor, terminal, QueueableCommand}; use crossterm::{QueueableCommand, cursor, terminal};
use std::{ use std::{
collections::HashSet, collections::HashSet,
env, env,
fs::{File, OpenOptions}, fs::{File, OpenOptions},
io::{Read, Seek, StdoutLock, Write}, io::{Read, Seek, StdoutLock, Write},
path::{Path, MAIN_SEPARATOR_STR}, path::{MAIN_SEPARATOR_STR, Path},
process::{Command, Stdio}, process::{Command, Stdio},
sync::{ sync::{
atomic::{AtomicUsize, Ordering::Relaxed}, atomic::{AtomicUsize, Ordering::Relaxed},
@ -427,7 +427,8 @@ impl AppState {
let next_exercise_ind = &next_exercise_ind; let next_exercise_ind = &next_exercise_ind;
let slf = &self; let slf = &self;
thread::Builder::new() thread::Builder::new()
.spawn_scoped(s, move || loop { .spawn_scoped(s, move || {
loop {
let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed); let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed);
let Some(exercise) = slf.exercises.get(exercise_ind) else { let Some(exercise) = slf.exercises.get(exercise_ind) else {
// No more exercises. // No more exercises.
@ -454,6 +455,7 @@ impl AppState {
{ {
break; break;
} }
}
}) })
.context("Failed to spawn a thread to check all exercises")?; .context("Failed to spawn a thread to check all exercises")?;
} }

View File

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
io::Read, io::Read,

View File

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use clap::Subcommand; use clap::Subcommand;
use std::path::PathBuf; use std::path::PathBuf;

View File

@ -1,8 +1,8 @@
use anyhow::{anyhow, bail, Context, Error, Result}; use anyhow::{Context, Error, Result, anyhow, bail};
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashSet, collections::HashSet,
fs::{self, read_dir, OpenOptions}, fs::{self, OpenOptions, read_dir},
io::{self, Read, Write}, io::{self, Read, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Command, Stdio}, process::{Command, Stdio},
@ -10,11 +10,11 @@ use std::{
}; };
use crate::{ 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, 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; const MAX_N_EXERCISES: usize = 999;
@ -42,10 +42,14 @@ fn check_cargo_toml(
if old_bins != new_bins { if old_bins != new_bins {
if cfg!(debug_assertions) { 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(()) Ok(())
@ -63,7 +67,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
bail!("Found an empty exercise name in `info.toml`"); bail!("Found an empty exercise name in `info.toml`");
} }
if name.len() > MAX_EXERCISE_NAME_LEN { 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) { if let Some(c) = forbidden_char(name) {
bail!("Char `{c}` in the exercise name `{name}` is not allowed"); bail!("Char `{c}` in the exercise name `{name}` is not allowed");
@ -79,7 +85,9 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
} }
if exercise_info.hint.trim_ascii().is_empty() { 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) { if !names.insert(name) {
@ -96,20 +104,28 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
.with_context(|| format!("Failed to read the file {path}"))?; .with_context(|| format!("Failed to read the file {path}"))?;
if !file_buf.contains("fn main()") { 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") { 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"); let contains_tests = file_buf.contains("#[test]\n");
if exercise_info.test { if exercise_info.test {
if !contains_tests { 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 { } 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(); file_buf.clear();
@ -125,7 +141,10 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
// Only one level of directory nesting is allowed. // Only one level of directory nesting is allowed.
fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> Result<()> { fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> Result<()> {
let unexpected_file = |path: &Path| { 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"))? { 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<PathBuf>) -> R
let path = entry.path(); let path = entry.path();
if !entry.file_type().unwrap().is_file() { 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(); 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<()> { fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> 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!(
Ordering::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"), "`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 => (), Ordering::Equal => (),
} }

View File

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use std::{ use std::{
env::set_current_dir, env::set_current_dir,
fs::{self, create_dir}, fs::{self, create_dir},
@ -6,7 +6,7 @@ use std::{
process::Command, 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. // Create a directory relative to the current directory and print its path.
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> { 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( write_rel_file(
"info.toml", "info.toml",
&dir_path_str, &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)?; write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?;
@ -130,7 +132,7 @@ bin = []
[package] [package]
name = "exercises" name = "exercises"
edition = "2021" edition = "2024"
# Don't publish the exercises on crates.io! # Don't publish the exercises on crates.io!
publish = false publish = false

View File

@ -1,13 +1,13 @@
use anyhow::Result; use anyhow::Result;
use crossterm::{ use crossterm::{
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
QueueableCommand, QueueableCommand,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
}; };
use std::io::{self, StdoutLock, Write}; use std::io::{self, StdoutLock, Write};
use crate::{ use crate::{
cmd::CmdRunner, 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. /// The initial capacity of the output buffer.

View File

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Error, Result}; use anyhow::{Context, Error, Result, bail};
use serde::Deserialize; use serde::Deserialize;
use std::{fs, io::ErrorKind}; use std::{fs, io::ErrorKind};

View File

@ -1,7 +1,7 @@
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use crossterm::{ use crossterm::{
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
QueueableCommand, QueueableCommand,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
}; };
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
@ -57,7 +57,9 @@ pub fn init() -> Result<()> {
if !workspace_manifest_content.contains("[workspace]\n") if !workspace_manifest_content.contains("[workspace]\n")
&& !workspace_manifest_content.contains("workspace.") && !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 ")?; 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()) .stdout(Stdio::null())
.status()?; .status()?;
if !status.success() { 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")?; stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?;

View File

@ -1,14 +1,13 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use crossterm::{ use crossterm::{
cursor, QueueableCommand, cursor,
event::{ event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, MouseEventKind, self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, MouseEventKind,
}, },
terminal::{ terminal::{
disable_raw_mode, enable_raw_mode, DisableLineWrap, EnableLineWrap, EnterAlternateScreen, DisableLineWrap, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen,
LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
}, },
QueueableCommand,
}; };
use std::io::{self, StdoutLock, Write}; use std::io::{self, StdoutLock, Write};

View File

@ -1,11 +1,11 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use crossterm::{ use crossterm::{
QueueableCommand,
cursor::{MoveTo, MoveToNextLine}, cursor::{MoveTo, MoveToNextLine},
style::{ style::{
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor, Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
}, },
terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate}, terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate},
QueueableCommand,
}; };
use std::{ use std::{
fmt::Write as _, fmt::Write as _,
@ -15,7 +15,7 @@ use std::{
use crate::{ use crate::{
app_state::AppState, app_state::AppState,
exercise::Exercise, exercise::Exercise,
term::{progress_bar, CountedWrite, MaxLenWriter}, term::{CountedWrite, MaxLenWriter, progress_bar},
}; };
use super::scroll_state::ScrollState; use super::scroll_state::ScrollState;

View File

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use app_state::StateFileStatus; use app_state::StateFileStatus;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::{ use std::{

View File

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use crossterm::{ use crossterm::{
style::{Color, ResetColor, SetForegroundColor},
QueueableCommand, QueueableCommand,
style::{Color, ResetColor, SetForegroundColor},
}; };
use std::{ use std::{
io::{self, Write}, io::{self, Write},
@ -10,7 +10,7 @@ use std::{
use crate::{ use crate::{
app_state::{AppState, ExercisesProgress}, 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<ExitCode> { pub fn run(app_state: &mut AppState) -> Result<ExitCode> {

View File

@ -1,8 +1,8 @@
use crossterm::{ use crossterm::{
Command, QueueableCommand,
cursor::MoveTo, cursor::MoveTo,
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor}, style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
terminal::{Clear, ClearType}, terminal::{Clear, ClearType},
Command, QueueableCommand,
}; };
use std::{ use std::{
fmt, fs, fmt, fs,

View File

@ -1,18 +1,18 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use notify::{ use notify::{
event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
Event, EventKind, Event, EventKind,
event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
}; };
use std::{ use std::{
sync::{ sync::{
atomic::Ordering::Relaxed, atomic::Ordering::Relaxed,
mpsc::{sync_channel, RecvTimeoutError, Sender, SyncSender}, mpsc::{RecvTimeoutError, Sender, SyncSender, sync_channel},
}, },
thread, thread,
time::Duration, time::Duration,
}; };
use super::{WatchEvent, EXERCISE_RUNNING}; use super::{EXERCISE_RUNNING, WatchEvent};
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200); const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);

View File

@ -1,24 +1,25 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use crossterm::{ use crossterm::{
QueueableCommand,
style::{ style::{
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor, Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
}, },
terminal, QueueableCommand, terminal,
}; };
use std::{ use std::{
io::{self, Read, StdoutLock, Write}, io::{self, Read, StdoutLock, Write},
sync::mpsc::{sync_channel, Sender, SyncSender}, sync::mpsc::{Sender, SyncSender, sync_channel},
thread, thread,
}; };
use crate::{ use crate::{
app_state::{AppState, ExercisesProgress}, app_state::{AppState, ExercisesProgress},
clear_terminal, clear_terminal,
exercise::{solution_link_line, RunnableExercise, OUTPUT_CAPACITY}, exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line},
term::progress_bar, 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() const HEADING_ATTRIBUTES: Attributes = Attributes::none()
.with(Attribute::Bold) .with(Attribute::Bold)

View File

@ -4,7 +4,7 @@ use std::sync::{
mpsc::{Receiver, Sender}, mpsc::{Receiver, Sender},
}; };
use super::{WatchEvent, EXERCISE_RUNNING}; use super::{EXERCISE_RUNNING, WatchEvent};
pub enum InputEvent { pub enum InputEvent {
Next, Next,

View File

@ -7,5 +7,5 @@ bin = [
[package] [package]
name = "test_exercises" name = "test_exercises"
edition = "2021" edition = "2024"
publish = false publish = false