Build your own shell - Codecrafters style
28 Jul 2024
I created my own shell in Rust using the Codecrafters Build your own shell challenge.
As part of my ongoing efforts to learn by doing, I came across the course on LinkedIn and resolved to try it out using Rust, another goal of mine. Here's my experience of completing the challenge, and also my thoughts on the Codecrafters platform as a whole.
Capabilities of the shell
But first, what can the shell do?
- REPL: Read-Evaluate-Print-Loop structure
- Handle the following builtin commands:
exit: exit shell with desired exit codeechotype: identify command as a shell builtin, or print its absolute pathpwdcd: both absolute and relative paths, and also~
- Run arbitrary programs present in
$PATHand print their output. - Gracefully handle unidentified commands or programs not in
$PATH.
The code is available on Github:
https://github.com/guru-das-s/codecrafters-shell.
Compiling and running it is quite straightforward, with cargo build and cargo
run.
There are more "extensions" in the pipeline: piping, history, autocompletion of commands, et c. Really excited to do those when they're available!
The Codecrafters platform
I am thoroughly impressed by the Codecrafters platform.
- Sign up was smooth, with Github login
- They provide skeleton code to begin the challenge with
- The shell is built up, stage by stage, with clear instructions for each stage
- A convenient one-stop shell script to compile and run your code is provided too
- The cli tool runs all the tests for a stage
locally before
push-ing - The tool also runs tests for all the previous stages to ensure nothing breaks
git push-ing to their server (originalready configured in the clone step) advances to the next stage
Working on the challenge
Making time to work on the challenge was challenging in itself with a full time job and other quotidian demands on my time. It was only during my vacation in India that I had the time and mindspace to finally take the plunge and start.
Visual Studio Code is a great coding tool for Rust, especially with the Rust extensions. Being able to see the return types of functions and of function parameters is highly useful, as are the real-time compilation errors and the compiler diagnostic messages. The vim mode emulation is adequate for my needs too.
I chose to go with the C-style mental model of having a list of function pointers for every shell builtin:
enum CmdHandler {
Exit(fn(i32)),
Echo(fn(&[&str])),
Type(fn(&HashMap<String, CmdHandler>, &str)),
Pwd(fn()),
Cd(fn(&str)),
}
And a hashmap to map builtin commands to their handlers:
let mut builtins: HashMap<String, CmdHandler> = HashMap::new();
This allowed me to add new commands easily and keep things neat and organized.
It was great to discover the Rust equivalents of a few well-known C library functions:
stat()–fs::metadata()exec()–std::process:Command::new()getenv()–std::process:env::var()
A pleasant surprise was that I did not have to write any code to support relative
filepaths for the cd builtin - the code for the absolute filepaths was sufficient.
The difficulty level for that stage is marked as Hard but I literally had to write
zero new code to pass that!
Closing thoughts
I feel quite proud of the fact that I got to build something tangible in Rust.
My earlier attempts at learning Rust were limited to solving Advent of Code 2023 problems. In my experience, it was quite thrilling to work on them in real time, i.e. in December when every day would bring forth a new problem, but once I "fell off the wagon" I found it difficult to keep my motivation levels up and feel excited about solving them. Being part of an active community seems like a great motivator for me.
Now that I'm done with the challenge, I've been checking out other solutions in Rust on Github to see what I can learn from them. I realize that my solution is very C-like (because I'm primarily a C programmer) and doesn't use many Rust-y features like attributes or traits. Learning a new programming language is like learning a new language in many ways - immersing oneself in well-written code accelerates and supplements self-learning.
I also appreciated the autogenerated email I received upon completion which had great suggestions on what to do next: retrying the challenge in a different language, sharing the experience (and code) with others, or trying a new challenge. I'm seriously considering taking up the final suggestion!
Nand2Tetris - Projects 1 to 3