Background
I've been doing a lot with Rust and web dev, both front and back, since doubling down on the language a couple of years ago. There are two months left in 2024 and it's time for a shift of gears.
For awhile I'm going to go low, like very low. This article by Tania Rascia served to inspire: Understanding Bits, Bytes, and Numerical Bases
I recommend reading Tania's work before continuing. She provides probably the easiest to understand explanation I've ever read. Like seriously, it's really good.
What are We Going to Make?
We're going to start making a Rust version of the hex dumper program that Tania creates in NodeJS. In this first piece, we're going to keep the goals very simple:
- read in the content of any file, given the file name from the user
- output the content of the file in hexadecimal
- output the total length of the data read, in bytes
Later, we'll look at embellishing the output to be more pretty. Perhaps, in the future, we'll even add a web based UI. For now, this thing will be a command line only application.
The Code
For the impatient amongst you, here's the final code:
// src/main.rs
// dependencies
use std::env::args;
use std::fs::File;
use std::io::{Read, Result};
// main function
fn main() -> Result<()> {
// get the file path from the command line arguments
let args: Vec<String> = args().collect();
let file_path = &args[1];
// read the file into a buffer
let mut file = File::open(file_path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
// convert each byte entry to hexadecimal
let hex_buffer: Vec<String> = buffer.iter().map(|byte| format!("{:02x}", byte)).collect();
// output the buffer
println!("Buffer contents (in hexadecimal): {:?}", hex_buffer);
// output the length of the buffer
println!("Buffer length: {} bytes", buffer.len());
Ok(())
}
Breaking it Down
Ok, let's break this down.
The program flows like this:
- takes the arguments received by the user, just using the tools from the Rust standard library, and puts them in a vector
- grabs the first argument from the args vector and binds it to a variable called
file_path
, this represents the file we want the contents of- we're sticking with the standard library here and not bringing in the
clap
crate, as our CLI needs are pretty trivial
- we're sticking with the standard library here and not bringing in the
- opens the file given the
file_name
; this actually gives us what's known as a file handle, something we can work with further - creates an empty buffer to read the file contents into
- the buffer will be a simple vector containing the byte representation of the file contents
- reads the contents into the buffer
- makes a new vector by converting the decimal representation of each byte entry into hexadecimal
- given our
hex_buffer
is a vector, we can use an iterator combined with a map function to transform the decimal bytes into their hexidecimal representation
- given our
- outputs a message along with the file contents, with each byte shown as a hexadecimal number
- outputs a message stating the length of the buffer, which is the number of bytes read
That's it!
There's more work to do, but that's enough for this article.
Conclusion
Thanks for reading, I hope this super simple introduction has helped improve your understanding of file I/O in Rust as well as how to convert the decimal representation of bytes into the corresponding hexadecimal representation.