17 Jan 24
Getting Started
This is another post in my series about learning Rust through Ruby. Or comparing Rust to Ruby in the context of learning the syntax and ecosystem. First thing we want to do is get things set up locally. Let’s dive in and compare.
With Ruby, you can simply install the runtime on your machine and begin. A lot of people in the community will use a version manager like RVM or rbenv. Rust doesn’t really have an equivalent in that you switch between different versions of Rust by semantic version. There are toolchains, which can be thought of as versions, but I’ll get into those in a bit. With Rust, you can have a fair bit of confidence that developing on the latest stable version is the way to go. Before we get too ahead of ourselves, let’s install Rust.
The recommended way to install Rust locally is via rustup. Essentially, you are running a shell script that installs an executable–the Rust compiler.
- Note: we didn’t simply say use homebrew or other package manager
Rustup gets us the latest “version” of the Rust compiler. Notice “versions” is in quotes. Officially, Rust calls these toolchains. A toolchain is made up of a release channel and the compilation targets. If you are on a Mac, your compilation target is MacOS. There are three release channels. Nightly, beta, and stable. Nightly is the state of the main branch. It is work in progress. Possibly incomplete. Bleeding edge. Beta can be thought of as complete and ready for the community to test. Stable is an official release blessed by the community.
If you are developing a library to share with the community, you may want to test it against beta for example. You may want to have your tests run against all three release channels. But when in development, it probably makes most sense to use stable. You can switch between the release channels using: rustup default nightly
for example. The equivalent in Ruby would be using your version manager to switch to a pre-release version of Ruby. As of this writing, an example would be Ruby 3.3.0-preview2. That would be similar to switching to the beta release channel of Rust.
You can see what version of Rust you have installed by asking the Rust compiler: rustc --version
. Side note, just like using a .ruby-version file in your project, you can use rust-toolchain file in your Rust project to specify the release channel you want to use.
Now that we have Rust installed and understand we’re going to use the “stable version”, let’s compare some of the ecosystem to Ruby.
If you have been using Ruby for any amount of time, you are familiar with Bundler and Gems and Gemfiles. Rust has similar concepts.
The Bundler equivalent is called Cargo and was installed via rustup
. Gems have an equivalent in Crates. Gemfiles have an equivalent in Cargo.toml files.
Cargo is the Rust package manager. It does more than just manage packages, but for now we’ll introduce it as the way you install the equivalent of Ruby Gems. You can see what version you have installed with: cargo --version
.
For example, say we are going to be working with JSON. We would run cargo install serde
. Serde is a serialization and deserialization library. The Ruby equivalent would be bundle install serde
. Not surprisingly, a Ruby Gem exists with that name but doesn’t offer the same functionality. That should give you an idea of what Cargo is compared to Bundler.
Crates are the equivalent of Gems. They are libraries and executables you can install on your machine and use in your projects. Crates.io is the canonical source.
Cargo.toml is the equivalent of a Gemfile. It has a corresponding .lock file, just the same as your Ruby’s Gemfile. The toml file lists all of the packages you want to use and claim as dependencies. You can specify versions, paths, etc… Just like in a Gemfiles.
Now that you have a summary of the Rust equivalent of common Ruby items, let’s take a closer look at Cargo. Cargo offers a lot of functionality.
Cargo allows us to lint our code. Cargo allows us to run the tests. Cargo allows us to format our code. As alluded to earlier, it is more than a package manager. Bundler is as well, but Cargo has more functionality in the Rust context, especially when paired with the compiler. The compiler really is wonderful. It’s a shift compared to the runtime-ness of Ruby, but lean into it. Once you get into the flow of utilizing it, it really is quite wonderful. Props to everyone that has made the Rust compiler friendly and useful. We’ll take a look at examples in future posts.
Let’s see some of this in action and along the way we’ll spell out the Ruby equivalent for comparison.
When we are ready to start a Rust project called my_project
, we’ll use: cargo new my_project
. I imagine some are already asking if that is the same as using Bundler to start a new RubyGem. The answer is yes, kinda. When starting a new project we basically have two options. The first is a self contained executable, referred to as a binary. The second is a library. The difference being that the intention behind a library is that it is going to be used by another project. Similar to Ruby Gems. You can write a Gem that is intended to be used by itself and not consumed by other Ruby code. It’s not the common use case, but it can be done.
In the example above, we are creating an executable that we do not intend to be used by other code, otherwise we would pass a flag to Cargo letting it know we are creating a library. I’ll cover the two options in more detail in a future post. The equivalent in Ruby being: bundle gem my_project
. Most should be familiar with what Bundler does in this situation. It makes a directory and sticks some files in it. Including the Gemfile. Cargo is going to do the same. It makes a directory. It creates a cargo.toml file. It also scaffolds out some more structure including some example code.
With Rust, because the end result gets compiled, we need an entry point. Basically, when the code is executed, what gets run first. Rust has a convention. In Ruby, that could be the first line of the Gem’s main file. In Rust, it is the main function inside the src/main.rs file.
If you are following along and ran the command to create a new Rust binary, you’ll notice the src/main.rs file already has some code in it. It’s a hello world example. Congratulations, you have now created a hello world app in Rust! All you have to do to compile and run it is use Cargo: cargo run
.
The equivalent in Ruby would be to create a gem using: bundle gem my_project_ruby
. Follow the prompts to add a linter, etc… Then open the lib/myprojectruby.rb file and under the comment “your code goes here” add puts "hello world"
. Then back on a command line run: ruby lib/my_project_ruby.rb
.
We did it! We installed Rust and compared the ecosystem to Ruby. We did the standard “hello world” thing. So far so good. In the next post, we’ll take a look at Strings in Rust. Sounds kinda boring right? In Ruby, a string is an object and is simply characters between quotes. Can you fill an entire post just talking about strings in Rust? Stay tuned…..
Music companion of this post: Boy Harsher - My Animal Original Score
What am I working on currently: I am working on a number of things at the moment, a CLI for a startup called Tembo, an API for my startup–Schemabook, and getting onboarded onto a Rust based system that uses WASM on the frontend.