diff --git a/Cargo.lock b/Cargo.lock index c2c15813..c690e819 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,9 +640,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.12+spec-1.1.0" +version = "1.0.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" dependencies = [ "indexmap", "serde_core", @@ -655,9 +655,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] diff --git a/Cargo.toml b/Cargo.toml index 25415ce4..068a3f49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.88" [workspace.dependencies] serde = { version = "1", features = ["derive"] } -toml = { version = "0.9", default-features = false, features = ["std", "parse", "serde"] } +toml = { version = "1", default-features = false, features = ["std", "parse", "serde"] } [package] name = "rustlings" diff --git a/rustlings-macros/src/lib.rs b/rustlings-macros/src/lib.rs index b20c6f1d..db758d59 100644 --- a/rustlings-macros/src/lib.rs +++ b/rustlings-macros/src/lib.rs @@ -3,14 +3,15 @@ use quote::quote; use serde::Deserialize; #[derive(Deserialize)] -struct ExerciseInfo { - name: String, - dir: String, +struct ExerciseInfo<'a> { + name: &'a str, + dir: &'a str, } #[derive(Deserialize)] -struct InfoFile { - exercises: Vec, +struct InfoFile<'a> { + #[serde(borrow)] + exercises: Vec>, } #[proc_macro] @@ -37,7 +38,7 @@ pub fn include_files(_: TokenStream) -> TokenStream { continue; } - dirs.push(exercise.dir.as_str()); + dirs.push(exercise.dir); *dir_ind = dirs.len() - 1; } diff --git a/src/app_state.rs b/src/app_state.rs index d654d042..71a4fbbf 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -54,7 +54,7 @@ pub struct AppState { exercises: Vec, // Caches the number of done exercises to avoid iterating over all exercises every time. n_done: u16, - final_message: String, + final_message: &'static str, state_file: File, // Preallocated buffer for reading and writing the state file. file_buf: Vec, @@ -66,7 +66,7 @@ pub struct AppState { impl AppState { pub fn new( exercise_infos: Vec, - final_message: String, + final_message: &'static str, ) -> Result<(Self, StateFileStatus)> { let cmd_runner = CmdRunner::build()?; let mut state_file = OpenOptions::new() @@ -87,34 +87,33 @@ impl AppState { // Leaking is not a problem because the `AppState` instance lives until // the end of the program. let path = exercise_info.path().leak(); - let name = exercise_info.name.leak(); - let dir = exercise_info.dir.map(|dir| &*dir.leak()); - let hint = exercise_info.hint.leak().trim_ascii(); + let hint = exercise_info.hint.trim_ascii(); let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| { let mut canonical_path; - if let Some(dir) = dir { + if let Some(dir) = exercise_info.dir { canonical_path = String::with_capacity( - 2 + dir_canonical_path.len() + dir.len() + name.len(), + 2 + dir_canonical_path.len() + dir.len() + exercise_info.name.len(), ); canonical_path.push_str(dir_canonical_path); canonical_path.push_str(MAIN_SEPARATOR_STR); canonical_path.push_str(dir); } else { - canonical_path = - String::with_capacity(1 + dir_canonical_path.len() + name.len()); + canonical_path = String::with_capacity( + 1 + dir_canonical_path.len() + exercise_info.name.len(), + ); canonical_path.push_str(dir_canonical_path); } canonical_path.push_str(MAIN_SEPARATOR_STR); - canonical_path.push_str(name); + canonical_path.push_str(exercise_info.name); canonical_path.push_str(".rs"); canonical_path }); Exercise { - dir, - name, + dir: exercise_info.dir, + name: exercise_info.name, path, canonical_path, test: exercise_info.test, @@ -616,7 +615,7 @@ mod tests { current_exercise_ind: 0, exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()], n_done: 0, - final_message: String::new(), + final_message: "", state_file: tempfile::tempfile().unwrap(), file_buf: Vec::new(), official_exercises: true, diff --git a/src/cargo_toml.rs b/src/cargo_toml.rs index ce0dfd0c..9297da82 100644 --- a/src/cargo_toml.rs +++ b/src/cargo_toml.rs @@ -38,7 +38,7 @@ pub fn append_bins( buf.extend_from_slice(b"\", path = \""); buf.extend_from_slice(exercise_path_prefix); buf.extend_from_slice(b"exercises/"); - if let Some(dir) = &exercise_info.dir { + if let Some(dir) = exercise_info.dir { buf.extend_from_slice(dir.as_bytes()); buf.push(b'/'); } @@ -56,7 +56,7 @@ pub fn append_bins( buf.extend_from_slice(b"\", path = \""); buf.extend_from_slice(exercise_path_prefix); buf.extend_from_slice(b"solutions/"); - if let Some(dir) = &exercise_info.dir { + if let Some(dir) = exercise_info.dir { buf.extend_from_slice(dir.as_bytes()); buf.push(b'/'); } @@ -106,19 +106,19 @@ mod tests { fn test_bins() { let exercise_infos = [ ExerciseInfo { - name: String::from("1"), + name: "1", dir: None, test: true, strict_clippy: true, - hint: String::new(), + hint: "", skip_check_unsolved: false, }, ExerciseInfo { - name: String::from("2"), - dir: Some(String::from("d")), + name: "2", + dir: Some("d"), test: false, strict_clippy: false, - hint: String::new(), + hint: "", skip_check_unsolved: false, }, ]; diff --git a/src/dev/check.rs b/src/dev/check.rs index f7111063..16d04563 100644 --- a/src/dev/check.rs +++ b/src/dev/check.rs @@ -63,7 +63,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> { let mut file_buf = String::with_capacity(1 << 14); for exercise_info in &info_file.exercises { - let name = exercise_info.name.as_str(); + let name = exercise_info.name; if name.is_empty() { bail!("Found an empty exercise name in `info.toml`"); } @@ -76,7 +76,7 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result> { bail!("Char `{c}` in the exercise name `{name}` is not allowed"); } - if let Some(dir) = &exercise_info.dir { + if let Some(dir) = exercise_info.dir { if dir.is_empty() { bail!("The exercise `{name}` has an empty dir name in `info.toml`"); } @@ -214,7 +214,7 @@ fn check_exercises_unsolved( Some( thread::Builder::new() .spawn(|| exercise_info.run_exercise(None, cmd_runner)) - .map(|handle| (exercise_info.name.as_str(), handle)), + .map(|handle| (exercise_info.name, handle)), ) }) .collect::, _>>() diff --git a/src/embedded.rs b/src/embedded.rs index 61a5f581..bee4119c 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -85,7 +85,7 @@ impl EmbeddedFiles { exercise_path.truncate(prefix.len()); exercise_path.push_str(dir.name); exercise_path.push('/'); - exercise_path.push_str(&exercise_info.name); + exercise_path.push_str(exercise_info.name); exercise_path.push_str(".rs"); fs::write(&exercise_path, exercise_files.exercise) @@ -141,13 +141,14 @@ mod tests { use super::*; #[derive(Deserialize)] - struct ExerciseInfo { - dir: String, + struct ExerciseInfo<'a> { + dir: &'a str, } #[derive(Deserialize)] - struct InfoFile { - exercises: Vec, + struct InfoFile<'a> { + #[serde(borrow)] + exercises: Vec>, } #[test] diff --git a/src/info_file.rs b/src/info_file.rs index 04e5d644..8a90fcca 100644 --- a/src/info_file.rs +++ b/src/info_file.rs @@ -8,9 +8,9 @@ use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise}; #[derive(Deserialize)] pub struct ExerciseInfo { /// Exercise's unique name. - pub name: String, + pub name: &'static str, /// Exercise's directory name inside the `exercises/` directory. - pub dir: Option, + pub dir: Option<&'static str>, /// Run `cargo test` on the exercise. #[serde(default = "default_true")] pub test: bool, @@ -18,7 +18,7 @@ pub struct ExerciseInfo { #[serde(default)] pub strict_clippy: bool, /// The exercise's hint to be shown to the user on request. - pub hint: String, + pub hint: &'static str, /// The exercise is already solved. Ignore it when checking that all exercises are unsolved. #[serde(default)] pub skip_check_unsolved: bool, @@ -31,7 +31,7 @@ const fn default_true() -> bool { impl ExerciseInfo { /// Path to the exercise file starting with the `exercises/` directory. pub fn path(&self) -> String { - let mut path = if let Some(dir) = &self.dir { + let mut path = if let Some(dir) = self.dir { // 14 = 10 + 1 + 3 // exercises/ + / + .rs let mut path = String::with_capacity(14 + dir.len() + self.name.len()); @@ -47,7 +47,7 @@ impl ExerciseInfo { path }; - path.push_str(&self.name); + path.push_str(self.name); path.push_str(".rs"); path @@ -57,12 +57,12 @@ impl ExerciseInfo { impl RunnableExercise for ExerciseInfo { #[inline] fn name(&self) -> &str { - &self.name + self.name } #[inline] fn dir(&self) -> Option<&str> { - self.dir.as_deref() + self.dir } #[inline] @@ -82,9 +82,9 @@ pub struct InfoFile { /// For possible breaking changes in the future for community exercises. pub format_version: u8, /// Shown to users when starting with the exercises. - pub welcome_message: Option, + pub welcome_message: Option<&'static str>, /// Shown to users after finishing all exercises. - pub final_message: Option, + pub final_message: Option<&'static str>, /// List of all exercises. pub exercises: Vec, } @@ -95,7 +95,8 @@ impl InfoFile { pub fn parse() -> Result { // Read a local `info.toml` if it exists. let slf = match fs::read_to_string("info.toml") { - Ok(file_content) => toml::de::from_str::(&file_content) + // Leaking is fine since `InfoFile` is used until the end of the program. + Ok(file_content) => toml::de::from_str::(file_content.leak()) .context("Failed to parse the `info.toml` file")?, Err(e) => { if e.kind() == ErrorKind::NotFound { diff --git a/src/init.rs b/src/init.rs index 16ea35e8..9ef211e1 100644 --- a/src/init.rs +++ b/src/init.rs @@ -8,7 +8,7 @@ use std::{ env::set_current_dir, fs::{self, create_dir}, io::{self, Write}, - path::{Path, PathBuf}, + path::Path, process::{Command, Stdio}, }; @@ -18,8 +18,9 @@ use crate::{ }; #[derive(Deserialize)] -struct CargoLocateProject { - root: PathBuf, +struct CargoLocateProject<'a> { + #[serde(borrow)] + root: &'a Path, } pub fn init() -> Result<()> { @@ -72,7 +73,7 @@ pub fn init() -> Result<()> { )? .root; - let workspace_manifest_content = fs::read_to_string(&workspace_manifest) + let workspace_manifest_content = fs::read_to_string(workspace_manifest) .with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?; if !workspace_manifest_content.contains("[workspace]") && !workspace_manifest_content.contains("workspace.") diff --git a/src/main.rs b/src/main.rs index ffd2dfa7..b541b68f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -128,7 +128,7 @@ fn main() -> Result { None } else { // For the notify event handler thread. - // Leaking is not a problem because the slice lives until the end of the program. + // Leaking is fine since the slice is used until the end of the program. Some( &*app_state .exercises()