Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
30 changes: 30 additions & 0 deletions exercises/24_file_io/README.md
Original file line number Diff line number Diff line change
@@ -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)
53 changes: 53 additions & 0 deletions exercises/24_file_io/file_io1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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()?;

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, SAMPLE_TEXT)?;
} 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(())
}
74 changes: 74 additions & 0 deletions exercises/24_file_io/file_io2.rs
Original file line number Diff line number Diff line change
@@ -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";
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()?;

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 lines_processed = 0;

for (index, line) in buffered_input_file.lines().enumerate() {
let line = line.inspect_err(|err| {
eprintln!("{} line parse 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);
})?;

lines_processed = line_number;
}

println!("{} : lines processed", lines_processed);
file_cleanup()
}

fn create_required_files() -> Result<(), std::io::Error> {
let file_path = Path::new(TEST_INPUT_FILE_NAME);

if !file_path.exists() {
fs::write(file_path, SAMPLE_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(())
}
78 changes: 78 additions & 0 deletions exercises/24_file_io/file_io3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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 path_buffer = sample_file_path();

// 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(), SAMPLE_TEXT.len() as u64);
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 = sample_file_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| {
eprintln!("Could not create directory: {:?}", x);
})?;
}

if !file_path.exists() {
fs::write(&file_path, SAMPLE_TEXT).inspect_err(|err| {
eprintln!("Couldn't create test file: {:?}", err);
})?;
}

Ok(())
}

fn file_cleanup() -> Result<(), std::io::Error> {
let path_buffer = sample_file_path();

if path_buffer.exists() {
fs::remove_file(&path_buffer).inspect(|_| {
println!("Test file removed");
})?;
}

if let Some(dir_path) = path_buffer.parent()
&& dir_path.exists()
{
fs::remove_dir(dir_path).inspect(|_| {
println!("Test dir removed");
})?;
}

Ok(())
}
42 changes: 41 additions & 1 deletion rustlings-macros/info.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1211,3 +1211,43 @@ name = "conversions5"
dir = "23_conversions"
hint = """
Add `AsRef<str>` or `AsMut<u32>` 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<String, std::io::Error>`. 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<String, std::io::Error>`.
"""

[[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<Metadata, std::io::Error>`, giving you access to creation time,
size (`.len()`), and permissions.
"""
52 changes: 52 additions & 0 deletions solutions/24_file_io/file_io1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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()?;

let read_str_result = fs::read_to_string(TEST_FILE_NAME);

match read_str_result {
Ok(contents) => {
assert_eq!(SAMPLE_TEXT, 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, SAMPLE_TEXT)?;
} 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(())
}
Loading