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.
But first, what can the shell do?
exit
: exit shell with desired exit codeecho
type
: identify command as a shell builtin, or print its absolute pathpwd
cd
: both absolute and relative paths, and also ~
$PATH
and print their output.$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!
I am thoroughly impressed by the Codecrafters platform.
push
-inggit push
-ing to their server (origin
already configured in the clone step)
advances to the next stageMaking 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!
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!