From 316311e08cb5cb7eb911fe39910ed1c50766722b Mon Sep 17 00:00:00 2001 From: Kivanc Gunalp Date: Sun, 26 Jul 2026 15:09:01 +0000 Subject: [PATCH 1/5] Add file I/O exercises (file_io1-3) Adds three new exercises under exercises/24_file_io/, as proposed in rust-lang/rustlings#2233: - file_io1: read a whole file with fs::read_to_string and handle the Result. - file_io2: read/write a file efficiently with BufReader/BufWriter. - file_io3: build a path with PathBuf and inspect it with .metadata(). Includes matching solutions and a README describing the learning goal of each exercise. --- exercises/24_file_io/README.md | 30 ++++++++++++ exercises/24_file_io/file_io1.rs | 51 +++++++++++++++++++++ exercises/24_file_io/file_io2.rs | 75 ++++++++++++++++++++++++++++++ exercises/24_file_io/file_io3.rs | 78 ++++++++++++++++++++++++++++++++ solutions/24_file_io/file_io1.rs | 50 ++++++++++++++++++++ solutions/24_file_io/file_io2.rs | 74 ++++++++++++++++++++++++++++++ solutions/24_file_io/file_io3.rs | 77 +++++++++++++++++++++++++++++++ 7 files changed, 435 insertions(+) create mode 100644 exercises/24_file_io/README.md create mode 100644 exercises/24_file_io/file_io1.rs create mode 100644 exercises/24_file_io/file_io2.rs create mode 100644 exercises/24_file_io/file_io3.rs create mode 100644 solutions/24_file_io/file_io1.rs create mode 100644 solutions/24_file_io/file_io2.rs create mode 100644 solutions/24_file_io/file_io3.rs diff --git a/exercises/24_file_io/README.md b/exercises/24_file_io/README.md new file mode 100644 index 0000000000..a6ef28a939 --- /dev/null +++ b/exercises/24_file_io/README.md @@ -0,0 +1,30 @@ +# File IO + +Rust provides several file I/O functions in the standard library. Buffered +reads and writes provide better performance by reducing the number of +underlying system calls. + +## Learning goals + +- **file_io1** — Read an entire file into memory with `fs::read_to_string` + and handle the `Result` it returns instead of unwrapping blindly, so you + get comfortable with the basic "open, read, handle the error" pattern + that most file I/O in Rust builds on. +- **file_io2** — Wrap a `File` in a `BufReader` to read it line by line and + a `BufWriter` to write the transformed output, so you understand why + buffering matters for I/O performance and how `BufRead::lines` lets you + process input incrementally instead of loading it all at once. +- **file_io3** — Build a path with `PathBuf`, then call `.metadata()` to + inspect properties like creation time, size, and permissions, so you can + work with the filesystem safely across platforms without hardcoding + path separators. + +## Further information + +Here is the documentation for these functions in the standard library. + +- [read_to_string](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) +- [BufReader](https://doc.rust-lang.org/std/io/struct.BufReader.html) +- [BufWriter](https://doc.rust-lang.org/std/io/struct.BufWriter.html) +- [Path](https://doc.rust-lang.org/stable/std/path/struct.Path.html) +- [PathBuf](https://doc.rust-lang.org/std/path/struct.PathBuf.html) diff --git a/exercises/24_file_io/file_io1.rs b/exercises/24_file_io/file_io1.rs new file mode 100644 index 0000000000..54647dc461 --- /dev/null +++ b/exercises/24_file_io/file_io1.rs @@ -0,0 +1,51 @@ +use std::fs; +use std::path::Path; + +const TEST_FILE_NAME: &str = "SampleTextFile.txt"; +fn main() -> Result<(), std::io::Error> { + create_required_files()?; + + let read_str_result = fs::read_to_string(TEST_FILE_NAME); + + match read_str_result { + Ok(contents) => { + // TODO : What would be the expected text ? + assert_eq!(, contents); + } + Err(err) => { + eprintln!("File read error. {}", err); + } + } + + file_cleanup()?; + Ok(()) +} + +fn create_required_files() -> Result<(), std::io::Error> { + let file_path = Path::new(TEST_FILE_NAME); + + if !file_path.exists() { + fs::write(file_path, "This is the file content.")?; + } else { + println!("File already exist."); + } + + Ok(()) +} + +fn file_cleanup() -> Result<(), std::io::Error> { + let file_path = Path::new(TEST_FILE_NAME); + + if file_path.exists() { + fs::remove_file(file_path).inspect(|_| { + println!("Test file {} deleted.", TEST_FILE_NAME); + })?; + } else { + println!( + "No cleanup necessary since {} not exist.", + file_path.display() + ); + } + + Ok(()) +} diff --git a/exercises/24_file_io/file_io2.rs b/exercises/24_file_io/file_io2.rs new file mode 100644 index 0000000000..f19d2a4784 --- /dev/null +++ b/exercises/24_file_io/file_io2.rs @@ -0,0 +1,75 @@ +use std::fs; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::path::Path; + +const TEST_INPUT_FILE_NAME: &str = "MultiLineTextFile.txt"; +const TEST_OUTPUT_FILE_NAME: &str = "MultiLineOutputFile.txt"; + +fn main() -> Result<(), std::io::Error> { + create_required_files()?; + + let input_file = fs::File::open(TEST_INPUT_FILE_NAME).inspect_err(|err| { + eprintln!("{} file open error {:?}", TEST_INPUT_FILE_NAME, err); + })?; + + // TODO : How to create a new BufReader using input file + let buffered_input_file =; + + let output_file = fs::File::create(TEST_OUTPUT_FILE_NAME).inspect_err(|err| { + eprintln!("{} file open error {:?}", TEST_OUTPUT_FILE_NAME, err); + })?; + + let mut buffered_file_writer = BufWriter::new(output_file); + + let mut line_number = 1; + + for line in buffered_input_file.lines() { + let line = line.inspect_err(|err| { + eprintln!("{} line parse error {:?}", TEST_INPUT_FILE_NAME, err); + })?; + + buffered_file_writer + .write(format!("Line {} : {}\n", line_number, line).as_bytes()) + .inspect_err(|err| { + eprintln!("{} line write error {:?}", TEST_INPUT_FILE_NAME, err); + })?; + + line_number += 1; + } + + println!("{} : lines processed", line_number - 1); + file_cleanup() +} + +fn create_required_files() -> Result<(), std::io::Error> { + let file_path = Path::new(TEST_INPUT_FILE_NAME); + + if !file_path.exists() { + let text = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; + fs::write(file_path, text).inspect_err(|err| { + eprintln!("Couldn't create the test file : {}", err); + })?; + } + + Ok(()) +} + +fn file_cleanup() -> Result<(), std::io::Error> { + let file_names = vec![TEST_INPUT_FILE_NAME, TEST_OUTPUT_FILE_NAME]; + + for file_name in file_names { + let file_path = Path::new(file_name); + + if file_path.exists() { + fs::remove_file(file_path).inspect(|_| { + println!("Test file {} removed", file_name); + })?; + } else { + println!("No cleanup necessary since {} not exist.", file_name); + } + } + + Ok(()) +} diff --git a/exercises/24_file_io/file_io3.rs b/exercises/24_file_io/file_io3.rs new file mode 100644 index 0000000000..7966bfcca1 --- /dev/null +++ b/exercises/24_file_io/file_io3.rs @@ -0,0 +1,78 @@ +use std::fs; +use std::io::Error; +use std::path::PathBuf; + +fn main() -> Result<(), std::io::Error> { + create_required_files()?; + let mut path_buffer = PathBuf::new(); + + path_buffer.push("SampleFilesFolder"); + path_buffer.push("MultiLineTextFile.txt"); + + // TODO : How to get metadata using path_buffer ? + let meta_data_result = path_buffer. + + match meta_data_result { + Ok(meta_data) => { + println!("Metadata about the file : {:?}", path_buffer); + println!("File creation time {:?}", meta_data.created()); + println!("File size {}", meta_data.len()); + assert_eq!(meta_data.len(), 117); + println!("File permissions {:?}", meta_data.permissions()); + assert!(!meta_data.permissions().readonly()); + } + Err(error) => { + eprintln!("Could not get metadata. Error: {:?}", error); + } + } + + file_cleanup() +} + +fn create_required_files() -> Result<(), std::io::Error> { + let file_path = PathBuf::from("SampleFilesFolder/MultiLineTextFile.txt"); + + let dir_path = match file_path.parent(){ + Some(parent) => parent, + None => return Err(Error::other("Could not get parent path")), + }; + + if !dir_path.exists() { + fs::create_dir(dir_path).inspect_err(|x| { + eprintln!("Could not create directory: {:?}", x); + })?; + } + + if !file_path.exists() { + let text = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; + fs::write(&file_path, text).inspect_err(|err| { + eprintln!("Couldn't create test file: {:?}", err); + })?; + } + + Ok(()) +} + +fn file_cleanup() -> Result<(), std::io::Error> { + let mut path_buffer = PathBuf::new(); + + path_buffer.push("SampleFilesFolder"); + path_buffer.push("MultiLineTextFile.txt"); + + if path_buffer.exists() { + fs::remove_file(&path_buffer).inspect(|_| { + println!("Test file removed"); + })?; + } + path_buffer.pop(); + + if path_buffer.exists() { + fs::remove_dir(&path_buffer).inspect(|_| { + println!("Test dir removed"); + })?; + } + + Ok(()) +} diff --git a/solutions/24_file_io/file_io1.rs b/solutions/24_file_io/file_io1.rs new file mode 100644 index 0000000000..9331eeea57 --- /dev/null +++ b/solutions/24_file_io/file_io1.rs @@ -0,0 +1,50 @@ +use std::fs; +use std::path::Path; + +const TEST_FILE_NAME: &str = "SampleTextFile.txt"; +fn main() -> Result<(), std::io::Error> { + create_required_files()?; + + let read_str_result = fs::read_to_string(TEST_FILE_NAME); + + match read_str_result { + Ok(contents) => { + assert_eq!("This is the file content.", contents); + } + Err(err) => { + eprintln!("File read error. {}", err); + } + } + + file_cleanup()?; + Ok(()) +} + +fn create_required_files() -> Result<(), std::io::Error> { + let file_path = Path::new(TEST_FILE_NAME); + + if !file_path.exists() { + fs::write(file_path, "This is the file content.")?; + } else { + println!("File already exist."); + } + + Ok(()) +} + +fn file_cleanup() -> Result<(), std::io::Error> { + let file_path = Path::new(TEST_FILE_NAME); + + if file_path.exists() { + fs::remove_file(file_path).inspect(|_| { + println!("Test file {} deleted.", TEST_FILE_NAME); + })?; + } else { + println!( + "No cleanup necessary since {} not exist.", + file_path.display() + ); + } + + Ok(()) +} diff --git a/solutions/24_file_io/file_io2.rs b/solutions/24_file_io/file_io2.rs new file mode 100644 index 0000000000..bfcaff1ead --- /dev/null +++ b/solutions/24_file_io/file_io2.rs @@ -0,0 +1,74 @@ +use std::fs; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::path::Path; + +const TEST_INPUT_FILE_NAME: &str = "MultiLineTextFile.txt"; +const TEST_OUTPUT_FILE_NAME: &str = "MultiLineOutputFile.txt"; + +fn main() -> Result<(), std::io::Error> { + create_required_files()?; + + let input_file = fs::File::open(TEST_INPUT_FILE_NAME).inspect_err(|err| { + eprintln!("{} file open error {:?}", TEST_INPUT_FILE_NAME, err); + })?; + + let buffered_input_file = BufReader::new(input_file); + + let output_file = fs::File::create(TEST_OUTPUT_FILE_NAME).inspect_err(|err| { + eprintln!("{} file open error {:?}", TEST_OUTPUT_FILE_NAME, err); + })?; + + let mut buffered_file_writer = BufWriter::new(output_file); + + let mut line_number = 1; + + for line in buffered_input_file.lines() { + let line = line.inspect_err(|err| { + eprintln!("{} line parse error {:?}", TEST_INPUT_FILE_NAME, err); + })?; + + buffered_file_writer + .write(format!("Line {} : {}\n", line_number, line).as_bytes()) + .inspect_err(|err| { + eprintln!("{} line write error {:?}", TEST_INPUT_FILE_NAME, err); + })?; + + line_number += 1; + } + + println!("{} : lines processed", line_number - 1); + file_cleanup() +} + +fn create_required_files() -> Result<(), std::io::Error> { + let file_path = Path::new(TEST_INPUT_FILE_NAME); + + if !file_path.exists() { + let text = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; + fs::write(file_path, text).inspect_err(|err| { + eprintln!("Couldn't create the test file : {}", err); + })?; + } + + Ok(()) +} + +fn file_cleanup() -> Result<(), std::io::Error> { + let file_names = vec![TEST_INPUT_FILE_NAME, TEST_OUTPUT_FILE_NAME]; + + for file_name in file_names { + let file_path = Path::new(file_name); + + if file_path.exists() { + fs::remove_file(file_path).inspect(|_| { + println!("Test file {} removed", file_name); + })?; + } else { + println!("No cleanup necessary since {} not exist.", file_name); + } + } + + Ok(()) +} diff --git a/solutions/24_file_io/file_io3.rs b/solutions/24_file_io/file_io3.rs new file mode 100644 index 0000000000..a68dad6efb --- /dev/null +++ b/solutions/24_file_io/file_io3.rs @@ -0,0 +1,77 @@ +use std::fs; +use std::io::Error; +use std::path::PathBuf; + +fn main() -> Result<(), std::io::Error> { + create_required_files()?; + let mut path_buffer = PathBuf::new(); + + path_buffer.push("SampleFilesFolder"); + path_buffer.push("MultiLineTextFile.txt"); + + let meta_data_result = path_buffer.metadata(); + + match meta_data_result { + Ok(meta_data) => { + println!("Metadata about the file : {:?}", path_buffer); + println!("File creation time {:?}", meta_data.created()); + println!("File size {}", meta_data.len()); + assert_eq!(meta_data.len(), 117); + println!("File permissions {:?}", meta_data.permissions()); + assert!(!meta_data.permissions().readonly()); + } + Err(error) => { + eprintln!("Could not get metadata. Error: {:?}", error); + } + } + + file_cleanup() +} + +fn create_required_files() -> Result<(), std::io::Error> { + let file_path = PathBuf::from("SampleFilesFolder/MultiLineTextFile.txt"); + + let dir_path = match file_path.parent() { + Some(parent) => parent, + None => return Err(Error::other("Could not get parent path")), + }; + + if !dir_path.exists() { + fs::create_dir(dir_path).inspect_err(|x| { + eprintln!("Could not create directory: {:?}", x); + })?; + } + + if !file_path.exists() { + let text = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; + fs::write(&file_path, text).inspect_err(|err| { + eprintln!("Couldn't create test file: {:?}", err); + })?; + } + + Ok(()) +} + +fn file_cleanup() -> Result<(), std::io::Error> { + let mut path_buffer = PathBuf::new(); + + path_buffer.push("SampleFilesFolder"); + path_buffer.push("MultiLineTextFile.txt"); + + if path_buffer.exists() { + fs::remove_file(&path_buffer).inspect(|_| { + println!("Test file removed"); + })?; + } + path_buffer.pop(); + + if path_buffer.exists() { + fs::remove_dir(&path_buffer).inspect(|_| { + println!("Test dir removed"); + })?; + } + + Ok(()) +} From 47bc98784bb0ce0c0a76f64a718775a245cec0f4 Mon Sep 17 00:00:00 2001 From: Kivanc Gunalp Date: Sun, 26 Jul 2026 15:09:07 +0000 Subject: [PATCH 2/5] Register file_io exercises in build config Adds file_io1-3 (and their solutions) to dev/Cargo.toml's bin list so 'cargo dev check'/'cargo dev update' pick them up, and registers them in rustlings-macros/info.toml with hints that explain each exercise's learning goal and point at the relevant std API. --- dev/Cargo.toml | 6 ++++++ rustlings-macros/info.toml | 42 +++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 66bc1dfea0..ee5f60fdff 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -188,6 +188,12 @@ bin = [ { name = "conversions4_sol", path = "../solutions/23_conversions/conversions4.rs" }, { name = "conversions5", path = "../exercises/23_conversions/conversions5.rs" }, { name = "conversions5_sol", path = "../solutions/23_conversions/conversions5.rs" }, + { name = "file_io1", path = "../exercises/24_file_io/file_io1.rs" }, + { name = "file_io1_sol", path = "../solutions/24_file_io/file_io1.rs" }, + { name = "file_io2", path = "../exercises/24_file_io/file_io2.rs" }, + { name = "file_io2_sol", path = "../solutions/24_file_io/file_io2.rs" }, + { name = "file_io3", path = "../exercises/24_file_io/file_io3.rs" }, + { name = "file_io3_sol", path = "../solutions/24_file_io/file_io3.rs" }, ] [package] diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 3a1cac3537..66725390e9 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -304,7 +304,7 @@ Now, you have another tool in your toolbox!""" name = "vecs1" dir = "05_vecs" hint = """ -In Rust, there are two basic ways to define a Vector. +In Rust, there are two ways to define a Vector. 1. One way is to use the `Vec::new()` function to create a new vector and fill it with the `push()` method. 2. The second way is to use the `vec![]` macro and define your elements @@ -1211,3 +1211,43 @@ name = "conversions5" dir = "23_conversions" hint = """ Add `AsRef` or `AsMut` as a trait bound to the functions.""" + +# File IO Exercises + +[[exercises]] +name = "file_io1" +dir = "24_file_io" +test = false +hint = """ +Learning goal: read a whole file into memory and handle the Result it +produces, rather than unwrapping it. + +`fs::read_to_string` returns a `Result`. Match on +it (or use `?`) to get the file's contents out of the `Ok` variant, and +compare that to the text that `create_required_files` wrote to the file. +""" + +[[exercises]] +name = "file_io2" +dir = "24_file_io" +test = false +hint = """ +Learning goal: read a file efficiently, line by line, using a BufReader, +and understand why buffering matters for I/O performance. + +`BufReader::new(input_file)` wraps a `File` so you can call `.lines()` on +it, which yields each line as a `Result`. +""" + +[[exercises]] +name = "file_io3" +dir = "24_file_io" +test = false +hint = """ +Learning goal: build a filesystem path with PathBuf and inspect the file +it points to without hardcoding platform-specific path separators. + +`Path`/`PathBuf` expose a `.metadata()` method that returns a +`Result`, giving you access to creation time, +size (`.len()`), and permissions. +""" From 68f8369cb4dd602c4f0be66a791584e4bda97fab Mon Sep 17 00:00:00 2001 From: Kivanc Gunalp Date: Sun, 26 Jul 2026 15:27:53 +0000 Subject: [PATCH 3/5] Harden file_io exercises: remove magic number, dedupe paths - file_io3: replace the hardcoded expected file size (117) with SAMPLE_TEXT.len(), so the check is derived from the actual content instead of an unexplained magic number. - file_io3: use create_dir_all instead of create_dir, and add a sample_file_path() helper shared by main/create_required_files/ file_cleanup to avoid three separate ad-hoc path constructions. - file_io1/file_io2: hoist the sample text into a SAMPLE_TEXT constant used by both the exercise and its solution, removing duplicated literals. No behavioral change: all three solutions were rebuilt and run locally, producing identical output (file size still 117, permissions still non-readonly) with no leftover files. --- exercises/24_file_io/file_io1.rs | 4 +++- exercises/24_file_io/file_io2.rs | 8 +++---- exercises/24_file_io/file_io3.rs | 41 ++++++++++++++++---------------- solutions/24_file_io/file_io1.rs | 6 +++-- solutions/24_file_io/file_io2.rs | 8 +++---- solutions/24_file_io/file_io3.rs | 39 +++++++++++++++--------------- 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/exercises/24_file_io/file_io1.rs b/exercises/24_file_io/file_io1.rs index 54647dc461..475008fe74 100644 --- a/exercises/24_file_io/file_io1.rs +++ b/exercises/24_file_io/file_io1.rs @@ -2,6 +2,8 @@ use std::fs; use std::path::Path; const TEST_FILE_NAME: &str = "SampleTextFile.txt"; +const SAMPLE_TEXT: &str = "This is the file content."; + fn main() -> Result<(), std::io::Error> { create_required_files()?; @@ -25,7 +27,7 @@ fn create_required_files() -> Result<(), std::io::Error> { let file_path = Path::new(TEST_FILE_NAME); if !file_path.exists() { - fs::write(file_path, "This is the file content.")?; + fs::write(file_path, SAMPLE_TEXT)?; } else { println!("File already exist."); } diff --git a/exercises/24_file_io/file_io2.rs b/exercises/24_file_io/file_io2.rs index f19d2a4784..279c17dc5f 100644 --- a/exercises/24_file_io/file_io2.rs +++ b/exercises/24_file_io/file_io2.rs @@ -4,6 +4,9 @@ use std::path::Path; const TEST_INPUT_FILE_NAME: &str = "MultiLineTextFile.txt"; const TEST_OUTPUT_FILE_NAME: &str = "MultiLineOutputFile.txt"; +const SAMPLE_TEXT: &str = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; fn main() -> Result<(), std::io::Error> { create_required_files()?; @@ -45,10 +48,7 @@ fn create_required_files() -> Result<(), std::io::Error> { let file_path = Path::new(TEST_INPUT_FILE_NAME); if !file_path.exists() { - let text = "This is the first line of the text. - This is the second line. - And this is the third and the last line."; - fs::write(file_path, text).inspect_err(|err| { + fs::write(file_path, SAMPLE_TEXT).inspect_err(|err| { eprintln!("Couldn't create the test file : {}", err); })?; } diff --git a/exercises/24_file_io/file_io3.rs b/exercises/24_file_io/file_io3.rs index 7966bfcca1..51df5a82e2 100644 --- a/exercises/24_file_io/file_io3.rs +++ b/exercises/24_file_io/file_io3.rs @@ -2,12 +2,18 @@ use std::fs; use std::io::Error; use std::path::PathBuf; +const SAMPLE_TEXT: &str = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; + +fn sample_file_path() -> PathBuf { + PathBuf::from("SampleFilesFolder/MultiLineTextFile.txt") +} + fn main() -> Result<(), std::io::Error> { create_required_files()?; - let mut path_buffer = PathBuf::new(); - path_buffer.push("SampleFilesFolder"); - path_buffer.push("MultiLineTextFile.txt"); + let path_buffer = sample_file_path(); // TODO : How to get metadata using path_buffer ? let meta_data_result = path_buffer. @@ -17,7 +23,7 @@ fn main() -> Result<(), std::io::Error> { println!("Metadata about the file : {:?}", path_buffer); println!("File creation time {:?}", meta_data.created()); println!("File size {}", meta_data.len()); - assert_eq!(meta_data.len(), 117); + assert_eq!(meta_data.len(), SAMPLE_TEXT.len() as u64); println!("File permissions {:?}", meta_data.permissions()); assert!(!meta_data.permissions().readonly()); } @@ -30,24 +36,21 @@ fn main() -> Result<(), std::io::Error> { } fn create_required_files() -> Result<(), std::io::Error> { - let file_path = PathBuf::from("SampleFilesFolder/MultiLineTextFile.txt"); + let file_path = sample_file_path(); - let dir_path = match file_path.parent(){ + let dir_path = match file_path.parent() { Some(parent) => parent, None => return Err(Error::other("Could not get parent path")), }; if !dir_path.exists() { - fs::create_dir(dir_path).inspect_err(|x| { + fs::create_dir_all(dir_path).inspect_err(|x| { eprintln!("Could not create directory: {:?}", x); })?; } if !file_path.exists() { - let text = "This is the first line of the text. - This is the second line. - And this is the third and the last line."; - fs::write(&file_path, text).inspect_err(|err| { + fs::write(&file_path, SAMPLE_TEXT).inspect_err(|err| { eprintln!("Couldn't create test file: {:?}", err); })?; } @@ -56,22 +59,20 @@ fn create_required_files() -> Result<(), std::io::Error> { } fn file_cleanup() -> Result<(), std::io::Error> { - let mut path_buffer = PathBuf::new(); - - path_buffer.push("SampleFilesFolder"); - path_buffer.push("MultiLineTextFile.txt"); + let path_buffer = sample_file_path(); if path_buffer.exists() { fs::remove_file(&path_buffer).inspect(|_| { println!("Test file removed"); })?; } - path_buffer.pop(); - if path_buffer.exists() { - fs::remove_dir(&path_buffer).inspect(|_| { - println!("Test dir removed"); - })?; + if let Some(dir_path) = path_buffer.parent() { + if dir_path.exists() { + fs::remove_dir(dir_path).inspect(|_| { + println!("Test dir removed"); + })?; + } } Ok(()) diff --git a/solutions/24_file_io/file_io1.rs b/solutions/24_file_io/file_io1.rs index 9331eeea57..55d8ceeef6 100644 --- a/solutions/24_file_io/file_io1.rs +++ b/solutions/24_file_io/file_io1.rs @@ -2,6 +2,8 @@ use std::fs; use std::path::Path; const TEST_FILE_NAME: &str = "SampleTextFile.txt"; +const SAMPLE_TEXT: &str = "This is the file content."; + fn main() -> Result<(), std::io::Error> { create_required_files()?; @@ -9,7 +11,7 @@ fn main() -> Result<(), std::io::Error> { match read_str_result { Ok(contents) => { - assert_eq!("This is the file content.", contents); + assert_eq!(SAMPLE_TEXT, contents); } Err(err) => { eprintln!("File read error. {}", err); @@ -24,7 +26,7 @@ fn create_required_files() -> Result<(), std::io::Error> { let file_path = Path::new(TEST_FILE_NAME); if !file_path.exists() { - fs::write(file_path, "This is the file content.")?; + fs::write(file_path, SAMPLE_TEXT)?; } else { println!("File already exist."); } diff --git a/solutions/24_file_io/file_io2.rs b/solutions/24_file_io/file_io2.rs index bfcaff1ead..fd67e70b18 100644 --- a/solutions/24_file_io/file_io2.rs +++ b/solutions/24_file_io/file_io2.rs @@ -4,6 +4,9 @@ use std::path::Path; const TEST_INPUT_FILE_NAME: &str = "MultiLineTextFile.txt"; const TEST_OUTPUT_FILE_NAME: &str = "MultiLineOutputFile.txt"; +const SAMPLE_TEXT: &str = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; fn main() -> Result<(), std::io::Error> { create_required_files()?; @@ -44,10 +47,7 @@ fn create_required_files() -> Result<(), std::io::Error> { let file_path = Path::new(TEST_INPUT_FILE_NAME); if !file_path.exists() { - let text = "This is the first line of the text. - This is the second line. - And this is the third and the last line."; - fs::write(file_path, text).inspect_err(|err| { + fs::write(file_path, SAMPLE_TEXT).inspect_err(|err| { eprintln!("Couldn't create the test file : {}", err); })?; } diff --git a/solutions/24_file_io/file_io3.rs b/solutions/24_file_io/file_io3.rs index a68dad6efb..d2b204323f 100644 --- a/solutions/24_file_io/file_io3.rs +++ b/solutions/24_file_io/file_io3.rs @@ -2,12 +2,18 @@ use std::fs; use std::io::Error; use std::path::PathBuf; +const SAMPLE_TEXT: &str = "This is the first line of the text. + This is the second line. + And this is the third and the last line."; + +fn sample_file_path() -> PathBuf { + PathBuf::from("SampleFilesFolder/MultiLineTextFile.txt") +} + fn main() -> Result<(), std::io::Error> { create_required_files()?; - let mut path_buffer = PathBuf::new(); - path_buffer.push("SampleFilesFolder"); - path_buffer.push("MultiLineTextFile.txt"); + let path_buffer = sample_file_path(); let meta_data_result = path_buffer.metadata(); @@ -16,7 +22,7 @@ fn main() -> Result<(), std::io::Error> { println!("Metadata about the file : {:?}", path_buffer); println!("File creation time {:?}", meta_data.created()); println!("File size {}", meta_data.len()); - assert_eq!(meta_data.len(), 117); + assert_eq!(meta_data.len(), SAMPLE_TEXT.len() as u64); println!("File permissions {:?}", meta_data.permissions()); assert!(!meta_data.permissions().readonly()); } @@ -29,7 +35,7 @@ fn main() -> Result<(), std::io::Error> { } fn create_required_files() -> Result<(), std::io::Error> { - let file_path = PathBuf::from("SampleFilesFolder/MultiLineTextFile.txt"); + let file_path = sample_file_path(); let dir_path = match file_path.parent() { Some(parent) => parent, @@ -37,16 +43,13 @@ fn create_required_files() -> Result<(), std::io::Error> { }; if !dir_path.exists() { - fs::create_dir(dir_path).inspect_err(|x| { + fs::create_dir_all(dir_path).inspect_err(|x| { eprintln!("Could not create directory: {:?}", x); })?; } if !file_path.exists() { - let text = "This is the first line of the text. - This is the second line. - And this is the third and the last line."; - fs::write(&file_path, text).inspect_err(|err| { + fs::write(&file_path, SAMPLE_TEXT).inspect_err(|err| { eprintln!("Couldn't create test file: {:?}", err); })?; } @@ -55,22 +58,20 @@ fn create_required_files() -> Result<(), std::io::Error> { } fn file_cleanup() -> Result<(), std::io::Error> { - let mut path_buffer = PathBuf::new(); - - path_buffer.push("SampleFilesFolder"); - path_buffer.push("MultiLineTextFile.txt"); + let path_buffer = sample_file_path(); if path_buffer.exists() { fs::remove_file(&path_buffer).inspect(|_| { println!("Test file removed"); })?; } - path_buffer.pop(); - if path_buffer.exists() { - fs::remove_dir(&path_buffer).inspect(|_| { - println!("Test dir removed"); - })?; + if let Some(dir_path) = path_buffer.parent() { + if dir_path.exists() { + fs::remove_dir(dir_path).inspect(|_| { + println!("Test dir removed"); + })?; + } } Ok(()) From e0859583b3a1209d9ccaee2e02827eae8d9ea49c Mon Sep 17 00:00:00 2001 From: Kivanc Gunalp Date: Sun, 26 Jul 2026 15:34:10 +0000 Subject: [PATCH 4/5] Fix clippy collapsible_if in file_io3 cleanup dev-check runs clippy with -D warnings against the solutions, which flagged the nested if let Some(dir_path) = ... { if dir_path.exists() ... } in file_cleanup as collapsible. Combined into a single if-let with a let-chain condition (edition = "2024", so this is available). Verified locally: compiles under --edition 2024, clippy is silent, and the binary still runs cleanly with file size 117 and no leftover files. --- exercises/24_file_io/file_io3.rs | 12 ++++++------ solutions/24_file_io/file_io3.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/exercises/24_file_io/file_io3.rs b/exercises/24_file_io/file_io3.rs index 51df5a82e2..0f25c03dcb 100644 --- a/exercises/24_file_io/file_io3.rs +++ b/exercises/24_file_io/file_io3.rs @@ -67,12 +67,12 @@ fn file_cleanup() -> Result<(), std::io::Error> { })?; } - if let Some(dir_path) = path_buffer.parent() { - if dir_path.exists() { - fs::remove_dir(dir_path).inspect(|_| { - println!("Test dir removed"); - })?; - } + if let Some(dir_path) = path_buffer.parent() + && dir_path.exists() + { + fs::remove_dir(dir_path).inspect(|_| { + println!("Test dir removed"); + })?; } Ok(()) diff --git a/solutions/24_file_io/file_io3.rs b/solutions/24_file_io/file_io3.rs index d2b204323f..c6a05d4e87 100644 --- a/solutions/24_file_io/file_io3.rs +++ b/solutions/24_file_io/file_io3.rs @@ -66,12 +66,12 @@ fn file_cleanup() -> Result<(), std::io::Error> { })?; } - if let Some(dir_path) = path_buffer.parent() { - if dir_path.exists() { - fs::remove_dir(dir_path).inspect(|_| { - println!("Test dir removed"); - })?; - } + if let Some(dir_path) = path_buffer.parent() + && dir_path.exists() + { + fs::remove_dir(dir_path).inspect(|_| { + println!("Test dir removed"); + })?; } Ok(()) From 4e9b5735ada47cc391b22a52ae12f6eb39c36fcc Mon Sep 17 00:00:00 2001 From: Kivanc Gunalp Date: Sun, 26 Jul 2026 15:48:35 +0000 Subject: [PATCH 5/5] Use more idiomatic Rust in file_io2/file_io3 - file_io2: replace the manual line_number counter with .lines().enumerate(), and replace buffered_file_writer.write(format!(...).as_bytes()) with writeln!, which both formats and guarantees a complete write (write_fmt loops internally, unlike a single Write::write call). - file_io3: replace the manual match on file_path.parent() with .ok_or_else(...)? for a more idiomatic Option-to-Result conversion. Verified locally: both solutions compile under edition 2024, clippy is silent, and running them still produces identical output (3 lines processed / 117-byte file, non-readonly permissions) with no leftover files. --- exercises/24_file_io/file_io2.rs | 17 ++++++++--------- exercises/24_file_io/file_io3.rs | 7 +++---- solutions/24_file_io/file_io2.rs | 17 ++++++++--------- solutions/24_file_io/file_io3.rs | 7 +++---- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/exercises/24_file_io/file_io2.rs b/exercises/24_file_io/file_io2.rs index 279c17dc5f..37680891da 100644 --- a/exercises/24_file_io/file_io2.rs +++ b/exercises/24_file_io/file_io2.rs @@ -24,23 +24,22 @@ fn main() -> Result<(), std::io::Error> { let mut buffered_file_writer = BufWriter::new(output_file); - let mut line_number = 1; + let mut lines_processed = 0; - for line in buffered_input_file.lines() { + for (index, line) in buffered_input_file.lines().enumerate() { let line = line.inspect_err(|err| { eprintln!("{} line parse error {:?}", TEST_INPUT_FILE_NAME, err); })?; - buffered_file_writer - .write(format!("Line {} : {}\n", line_number, line).as_bytes()) - .inspect_err(|err| { - eprintln!("{} line write error {:?}", TEST_INPUT_FILE_NAME, err); - })?; + let line_number = index + 1; + writeln!(buffered_file_writer, "Line {} : {}", line_number, line).inspect_err(|err| { + eprintln!("{} line write error {:?}", TEST_INPUT_FILE_NAME, err); + })?; - line_number += 1; + lines_processed = line_number; } - println!("{} : lines processed", line_number - 1); + println!("{} : lines processed", lines_processed); file_cleanup() } diff --git a/exercises/24_file_io/file_io3.rs b/exercises/24_file_io/file_io3.rs index 0f25c03dcb..52cc8ed0d4 100644 --- a/exercises/24_file_io/file_io3.rs +++ b/exercises/24_file_io/file_io3.rs @@ -38,10 +38,9 @@ fn main() -> Result<(), std::io::Error> { fn create_required_files() -> Result<(), std::io::Error> { let file_path = sample_file_path(); - let dir_path = match file_path.parent() { - Some(parent) => parent, - None => return Err(Error::other("Could not get parent path")), - }; + let dir_path = file_path + .parent() + .ok_or_else(|| Error::other("Could not get parent path"))?; if !dir_path.exists() { fs::create_dir_all(dir_path).inspect_err(|x| { diff --git a/solutions/24_file_io/file_io2.rs b/solutions/24_file_io/file_io2.rs index fd67e70b18..4b2cb0f7e8 100644 --- a/solutions/24_file_io/file_io2.rs +++ b/solutions/24_file_io/file_io2.rs @@ -23,23 +23,22 @@ fn main() -> Result<(), std::io::Error> { let mut buffered_file_writer = BufWriter::new(output_file); - let mut line_number = 1; + let mut lines_processed = 0; - for line in buffered_input_file.lines() { + for (index, line) in buffered_input_file.lines().enumerate() { let line = line.inspect_err(|err| { eprintln!("{} line parse error {:?}", TEST_INPUT_FILE_NAME, err); })?; - buffered_file_writer - .write(format!("Line {} : {}\n", line_number, line).as_bytes()) - .inspect_err(|err| { - eprintln!("{} line write error {:?}", TEST_INPUT_FILE_NAME, err); - })?; + let line_number = index + 1; + writeln!(buffered_file_writer, "Line {} : {}", line_number, line).inspect_err(|err| { + eprintln!("{} line write error {:?}", TEST_INPUT_FILE_NAME, err); + })?; - line_number += 1; + lines_processed = line_number; } - println!("{} : lines processed", line_number - 1); + println!("{} : lines processed", lines_processed); file_cleanup() } diff --git a/solutions/24_file_io/file_io3.rs b/solutions/24_file_io/file_io3.rs index c6a05d4e87..67557696a5 100644 --- a/solutions/24_file_io/file_io3.rs +++ b/solutions/24_file_io/file_io3.rs @@ -37,10 +37,9 @@ fn main() -> Result<(), std::io::Error> { fn create_required_files() -> Result<(), std::io::Error> { let file_path = sample_file_path(); - let dir_path = match file_path.parent() { - Some(parent) => parent, - None => return Err(Error::other("Could not get parent path")), - }; + let dir_path = file_path + .parent() + .ok_or_else(|| Error::other("Could not get parent path"))?; if !dir_path.exists() { fs::create_dir_all(dir_path).inspect_err(|x| {