written July 2018
I'm excited to tell you about my recently-completed project to Make a Lisp using the Rust programming language: mal-rust.
There is already a great Rust implementation in the main mal repo, but I wanted to use this as an exercise to learn more about Rust, and so I purposely did not look at the already-completed implementation.[1]
Here I will document some of my experience and things I've learned in the process, and some tips if you are new to Rust too!
I've toyed with Rust several times before, and it usually ended with my hands in the air, frustrated at the borrow checker. Also, every beginner Rust tutorial you read has a big warning about the borrow checker and its learning curve.
I don't know exactly what was different this time, other than maybe my own expectations. I went into the project thinking, even feeling that my success with Rust will depend on my understanding the borrow checker. And, in the end, I believe him and I came to a sort of understanding!
I came to appreciate the borrow checker as a friend with a few very simple rules. And I finally read up on Rc and RefCell, which allow one to write code that still satisfy those rules!
When the borrow checker and I couldn't come to terms, then Rc
and clone()
allowed me to satisfy him in a safe way.
It's a nice dance, and I learned to love it.[2]
Tip: Learn to appreciate the borrow checker's errors. They will help you understand ownership and lifetimes better than any tutorial can!
I've read numerous tutorials about Rust lifetimes, and I think I understand the concept. But I still struggle with the syntax for explicitly annotating lifetimes.
Good news is, I was able to finish the project without needing to annotate lifetimes at all. Well, I think that's good news.
Tip: I don't know if I should feel bad, but I'm currently of the opinion that annotating lifetimes is something that the Rust masters do. I'll update this document when I meet one (or become one, haha).
I wish I had Rust's pattern matching ability in Ruby and JavaScript. That is all!
Tip: Rust's pattern matching can not only match on structs and enums, but nested levels of tuples and structs and enums! Give it a try!
Rust doesn't have inheritance like every other language I've used. Instead, you are encouraged to use composition for enhancement. This was super weird for me.
Tip: If, like me, composition is a pattern you don't use regularly, then expect to dedicate some time to struggle through it, like I did. It's worth it!
Friction for adding tests to your code is very low thanks to the test runner built into Cargo. While testing statically-typed Rust code isn't quite as critical as testing dynamically-typed Ruby code, testing still helped me catch and fix bugs quickly.
I set up an observr
process in a split window to run cargo test && cargo build
every time
I saved a file. This made my red-green-refactor loop much tighter!
Tip: Run your tests automatically when you save a file.
If you don't have an IDE that will do it, then you can gem
install observr
and use my script here.
I really enjoyed learning some Rust. I know I have a long way to go, but this was a good step.
Rust is a uniquely mind-bending language. It's good to have it in your tool belt, even if for the purpose of learning and appreciating the composition pattern, data ownership, and limiting shared mutable state.
mal is an amazing project, and I recommend you use mal as a starting point for learning any new programming language. It will force you to learn more of the language than any small project might, and the mal guide is detailed enough to eliminate a lot of wheel spinning about project architecture.[3]
Notes:
Hosting for this site is provided by