From 9e76c96ef869a204f3b2cc16bfb490d72f662f5f Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 4 Aug 2026 23:06:38 +0200 Subject: [PATCH] async1: Read input from files --- dev/Cargo.toml | 2 +- exercises/24_async/async1.rs | 59 +++++++++++++++++----------------- input_files/scores_class_a.txt | 3 ++ input_files/scores_class_b.txt | 3 ++ input_files/scores_class_c.txt | 3 ++ rustlings-macros/info.toml | 9 ++++++ rustlings-macros/src/lib.rs | 20 +++++++++--- solutions/24_async/async1.rs | 58 ++++++++++++++++----------------- src/embedded.rs | 7 ++++ src/init.rs | 9 ++++++ 10 files changed, 107 insertions(+), 66 deletions(-) create mode 100644 input_files/scores_class_a.txt create mode 100644 input_files/scores_class_b.txt create mode 100644 input_files/scores_class_c.txt diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 168fc10a..fe334b16 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -199,7 +199,7 @@ edition = "2024" publish = false [dependencies] -tokio = { version = "1", features = ["rt"] } +tokio = { version = "1", features = ["fs", "macros", "rt", "rt-multi-thread"] } [profile.release] panic = "abort" diff --git a/exercises/24_async/async1.rs b/exercises/24_async/async1.rs index 8c2067ed..01183920 100644 --- a/exercises/24_async/async1.rs +++ b/exercises/24_async/async1.rs @@ -4,39 +4,38 @@ // together, they can finish the job much faster. // // Let's simulate this using asynchronous programming. Each person is -// represented as an asynchronous task, which can be executed concurrently (i.e. -// they can be doing the calculations at the same time). - -fn main() { - // Async tasks need to be executed by a "runtime", which is not provided by - // Rust's standard library. Here, we use the mainstream runtime `tokio`. - let rt = tokio::runtime::Builder::new_current_thread() - .build() - .unwrap(); - - let scores_class_a = &[83, 77, 92]; - let scores_class_b = &[84, 88, 96]; - let scores_class_c = &[71, 83, 76]; +// represented as an asynchronous task, which can be executed concurrently. +// Async tasks need to be executed by a "runtime", which is not provided by +// Rust's standard library. Here, we use the mainstream runtime `tokio`. +// The macro `tokio::main` wraps the entire main function in a runtime. +#[tokio::main] +async fn main() { // TODO: Fix the compiler errors by making the spawned function async. - let alice = rt.spawn(calculate_mean_score(scores_class_a)); - let bob = rt.spawn(calculate_mean_score(scores_class_b)); - let catherine = rt.spawn(calculate_mean_score(scores_class_c)); + let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt")); + let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt")); + let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt")); - // Block the runtime on a task that awaits all three calculations. - let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async { - [ - // TODO: "await" all three tasks to fix the compiler error. - alice, bob, catherine, - ] - }); - - assert_eq!(mean_score_a, 84); - assert_eq!(mean_score_b, 89); - assert_eq!(mean_score_c, 76); + // TODO: Await the spawned tasks to check their results. + assert_eq!(mean_score_a, 84); // alice + assert_eq!(mean_score_b, 89); // bob + assert_eq!(mean_score_c, 76); // catherine } -fn calculate_mean_score(score_list: &[usize]) -> usize { - let score_sum: usize = score_list.iter().sum(); - score_sum / score_list.len() +// TODO: Fix the compiler errors by making the spawned function async. +fn calculate_mean_score(scores_file: &str) -> usize { + // Read the file asynchronously + let file = tokio::fs::read_to_string(scores_file).await.unwrap(); + + // Initialize the sum and the number of scores + let mut sum = 0; + let mut n = 0; + for line in file.lines() { + // Parse every line as a score + let score = line.parse::().unwrap(); + sum += score; + n += 1; + } + + sum / n } diff --git a/input_files/scores_class_a.txt b/input_files/scores_class_a.txt new file mode 100644 index 00000000..29fc7d8f --- /dev/null +++ b/input_files/scores_class_a.txt @@ -0,0 +1,3 @@ +83 +77 +92 diff --git a/input_files/scores_class_b.txt b/input_files/scores_class_b.txt new file mode 100644 index 00000000..7bbc2e8e --- /dev/null +++ b/input_files/scores_class_b.txt @@ -0,0 +1,3 @@ +84 +88 +96 diff --git a/input_files/scores_class_c.txt b/input_files/scores_class_c.txt new file mode 100644 index 00000000..cda6e810 --- /dev/null +++ b/input_files/scores_class_c.txt @@ -0,0 +1,3 @@ +71 +83 +76 diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 533b1d50..d31fb4c7 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -1225,3 +1225,12 @@ the functions "tim", "carl" and "nick". An async task can wait for another one to complete by "awaiting" it. Add ".await" after the three "task_name" variables in the "block_on" call.""" + +[[input_files]] +name = "scores_class_a.txt" + +[[input_files]] +name = "scores_class_b.txt" + +[[input_files]] +name = "scores_class_c.txt" diff --git a/rustlings-macros/src/lib.rs b/rustlings-macros/src/lib.rs index db758d59..a4104851 100644 --- a/rustlings-macros/src/lib.rs +++ b/rustlings-macros/src/lib.rs @@ -8,18 +8,23 @@ struct ExerciseInfo<'a> { dir: &'a str, } +#[derive(Deserialize)] +struct InputFileInfo<'a> { + name: &'a str, +} + #[derive(Deserialize)] struct InfoFile<'a> { #[serde(borrow)] exercises: Vec>, + input_files: Vec>, } #[proc_macro] pub fn include_files(_: TokenStream) -> TokenStream { let info_file = include_str!("../info.toml"); - let exercises = toml::de::from_str::(info_file) - .expect("Failed to parse `info.toml`") - .exercises; + let info = toml::de::from_str::(info_file).expect("Failed to parse `info.toml`"); + let exercises = info.exercises; let exercise_files = exercises .iter() @@ -46,11 +51,18 @@ pub fn include_files(_: TokenStream) -> TokenStream { .iter() .map(|dir| format!("../exercises/{dir}/README.md")); + let input_file_names = info.input_files.iter().map(|f| f.name); + let input_file_paths = info + .input_files + .iter() + .map(|f| format!("../input_files/{}", f.name)); + quote! { EmbeddedFiles { info_file: #info_file, exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*], - exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*] + exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*], + input_files: &[#(InputFile { name: #input_file_names, content: include_str!(#input_file_paths) }),*], } } .into() diff --git a/solutions/24_async/async1.rs b/solutions/24_async/async1.rs index 925be766..9321447a 100644 --- a/solutions/24_async/async1.rs +++ b/solutions/24_async/async1.rs @@ -4,39 +4,35 @@ // together, they can finish the job much faster. // // Let's simulate this using asynchronous programming. Each person is -// represented as an asynchronous task, which can be executed concurrently (i.e. -// they can be doing the calculations at the same time). +// represented as an asynchronous task, which can be executed concurrently. -fn main() { - // Async tasks need to be executed by a "runtime", which is not provided by - // Rust's standard library. Here, we use the mainstream runtime `tokio`. - let rt = tokio::runtime::Builder::new_current_thread() - .build() - .unwrap(); +// Async tasks need to be executed by a "runtime", which is not provided by +// Rust's standard library. Here, we use the mainstream runtime `tokio`. +// The macro `tokio::main` wraps the entire main function in a runtime. +#[tokio::main] +async fn main() { + let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt")); + let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt")); + let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt")); - let scores_class_a = &[83, 77, 92]; - let scores_class_b = &[84, 88, 96]; - let scores_class_c = &[71, 83, 76]; - - let alice = rt.spawn(calculate_mean_score(scores_class_a)); - let bob = rt.spawn(calculate_mean_score(scores_class_b)); - let catherine = rt.spawn(calculate_mean_score(scores_class_c)); - - // Block the runtime on a task that awaits all three calculations. - let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async { - [ - alice.await.unwrap(), - bob.await.unwrap(), - catherine.await.unwrap(), - ] - }); - - assert_eq!(mean_score_a, 84); - assert_eq!(mean_score_b, 89); - assert_eq!(mean_score_c, 76); + assert_eq!(mean_score_a.await.unwrap(), 84); // alice + assert_eq!(mean_score_b.await.unwrap(), 89); // bob + assert_eq!(mean_score_c.await.unwrap(), 76); // catherine } -async fn calculate_mean_score(score_list: &[usize]) -> usize { - let score_sum: usize = score_list.iter().sum(); - score_sum / score_list.len() +async fn calculate_mean_score(scores_file: &str) -> usize { + // Read the file asynchronously + let file = tokio::fs::read_to_string(scores_file).await.unwrap(); + + // Initialize the sum and the number of scores + let mut sum = 0; + let mut n = 0; + for line in file.lines() { + // Parse every line as a score + let score = line.parse::().unwrap(); + sum += score; + n += 1; + } + + sum / n } diff --git a/src/embedded.rs b/src/embedded.rs index bee4119c..3e0c0087 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -19,6 +19,12 @@ struct ExerciseFiles { dir_ind: usize, } +// Input files that may be read by exercises. +pub struct InputFile { + pub name: &'static str, + pub content: &'static str, +} + fn create_dir_if_not_exists(path: &str) -> Result<()> { if let Err(e) = create_dir(path) && e.kind() != io::ErrorKind::AlreadyExists @@ -58,6 +64,7 @@ pub struct EmbeddedFiles { pub info_file: &'static str, exercise_files: &'static [ExerciseFiles], pub exercise_dirs: &'static [ExerciseDir], + pub input_files: &'static [InputFile], } impl EmbeddedFiles { diff --git a/src/init.rs b/src/init.rs index 3ccef756..fe4332fb 100644 --- a/src/init.rs +++ b/src/init.rs @@ -144,6 +144,15 @@ pub fn init() -> Result<()> { .with_context(|| format!("Failed to create the file {solution_path}"))?; } + // init input files + create_dir("input_files").context("Failed to create the directory `input_files`")?; + for input_file in EMBEDDED_FILES.input_files { + fs::write( + format!("input_files/{}", input_file.name), + input_file.content, + )?; + } + let current_cargo_toml = include_str!("../dev-Cargo.toml"); // Skip the first line (comment). let newline_ind = current_cargo_toml