John Owolabi Idogun
Posted on June 15, 2021
Prelude
Rust is an imperative, super fast, and type-safe programming language that empowers you — a Software Engineer — "to reach farther, to program with confidence in a wider variety of domains than you did before." No wonder it has consistently maintained its deserved spot as the most loved programming language for half a decade!
What we are building
Using barebone rust
code, we will be building a simple command line key-value data store
like Redis
. It should take in two command line arguments and assign the first as the key
while the second, value
.
If installed on your machine, it can be used as follows:
┌──(sirneij@sirneij)-[~/Documents/Projects/rust-kvstore]
└─$[sirneij@sirneij rust-kvstore]$ rust-kvstore needle haystack
This should create a file, aptly named kv.db
. It's content can then be read:
┌──(sirneij@sirneij)-[~/Documents/Projects/rust-kvstore]
└─$[sirneij@sirneij rust-kvstore]$ cat kv.db
Whose output should look like:
───────┬─────────────────────────────────────────────────────
│ File: kv.db
───────┼─────────────────────────────────────────────────────
1 │ needle haystack
───────┴─────────────────────────────────────────────────────
However, if you have the source files, you can simply build
and run
it using:
┌──(sirneij@sirneij)-[~/Documents/Projects/rust-kvstore]
└─$[sirneij@sirneij rust-kvstore]$ cargo run needle haystack
This is simply for learning sake and no other motive is intended.
DECLAIMER
This example is based off of a two-part tutorial anchored by the beloved Ryan Levick. The only significant additions are: the use of a Vec<String>
instead of Iterator<Item = String>
; fixing this bug
thread 'main' panicked at 'Corrupt database: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:12:40
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
by checking if kv.db
already exists and if not create it using Rust's PathBuf standard library; using a more efficient file reader; and using split_once() instead of rsplit() among others.
It is highly recommended to check out the awesome livestreams on youtube. I must confess, it was a total tear down and dissection.
Assumptions
It is assumed that you have read The Rust book to some extent or have checked out the awesome livestreams on youtube by Ryan Levick.
Source code
As usual, you can get the full version of the source files for this article on
Sirneij / rust-kvstore
A simple key-value store such as Redis, implemented using the rust programming language
. Just clone it:
┌──(sirneij@sirneij)-[~/Documents/Projects]
└─$[sirneij@sirneij Projects]$ git clone https://github.com/Sirneij/rust-kvstore.git
and open it in your favourite text editor, mine is vs code.
┌──(sirneij@sirneij)-[~/Documents/Projects]
└─$[sirneij@sirneij Projects]$ cd rust-kvstore && code .
Proper implementation
Going by the assumptions made above, I will only point out some of my inputs.
- Taking arguments as vectors:
Since our little project wants to get two command line arguments, Rust provides a function args()
which can be found in the std::env
library. This function returns an iterator of the command line arguments. The .collect()
converts the returned iterator into a vector
. Its implementation for this project looks this way:
fn main(){
let args: Vec<String> = std::env::args().collect();
let key = &args[1];
let value = &args[2];
}
It should be noted that &args[0]
gives the path to our executable
which in this case should be "target/debug/rust-kvstore"
. You can see what is in args
by printing it to the console using println!()
macro:
fn main(){
let args: Vec<String> = std::env::args().collect();
println!("{:?}", args);
let key = &args[1];
let value = &args[2];
}
You should see something like:
["target/debug/rust-kvstore", "needle", "haystack"]
if you pass needle haystack
as arguments using cargo run
like so:
┌──(sirneij@sirneij)-[~/Documents/Projects/rust-kvstore]
└─$[sirneij@sirneij rust-kvstore]$ cargo run needle haystack
Since we are only concerned with arguments we passed, which starts from &args[1]
, we overlooked &args[0]
.
-
Fixing "No such file or directory" bug:
If
kv.db
is not manually created or not present at the start of the program's usage, an error of this form will surface:
thread 'main' panicked at 'Corrupt database: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:12:40
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
To fix this, we need to check whether or not kv.db
has been created. If not create it on the fly. To accomplish this, we use this awesome std::path::PathBuf
library in our "constructor", new()
.
impl Database {
fn new() -> Result<Database, std::io::Error> {
...
let mut contents = String::new();
let path = PathBuf::from("kv.db");
if path.exists() {
let file = std::fs::File::open(path)?;
let mut buf_reader = std::io::BufReader::new(file);
buf_reader.read_to_string(&mut contents)?;
} else {
std::fs::File::create("kv.db")?;
}
...
}
}
Using the more efficient
std::io::BufReader
It can also be seen in the snippet above that instead of using thestd::read_to_string
, we opted for the more efficientstd::io::BufReader::new(file);
split_once() implemented:
When Ryan was livestreaming,split_once()
was only available in thenightly
version of Rust so he opted forrsplit()
. However, I think it is stable now and the full implementation is shown as follows:
...
for line in contents.lines() {
let (key, value) = line.split_once("\t").expect("Corrupt database");
map.insert(key.to_string(), value.to_string());
}
...
That's it! Nifty and awesome. To build the project from scratch, code along with Ryan Levick..
Kindly drop your comments, reactions and suggestions. Make me a better writer.
Outro
Enjoyed this article? I'm a Software Engineer and Technical Writer actively seeking new opportunities, particularly in areas related to web security, finance, healthcare, and education. If you think my expertise aligns with your team's needs, let's chat! You can find me on LinkedIn and Twitter.
If you found this article valuable, consider sharing it with your network to help spread the knowledge!
References
- Introduction to Rust - by Ryan Levick
- The Rust Programming Language - by Steve Klabnik and Carol Nichols, with contributions from the Rust Community
- Stack Overflow Developer Survey - Most Loved, Dreaded, and Wanted Languages
Attributions
- Cover image made by Flat Icons
Posted on June 15, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.