Let's Learn Rust

Let's Learn Rust

In my professional career of more than 30 years, I have learned and used a litany of programming languages too numerous to reasonably list. Throughout that history, I have tended to learn languages in a similar manner. Typically, I read enough to start understanding the basics, and then I dive in and start writing code. A key part of this voyage often involves rewriting something that I have already written in a familiar language into the new language.

I have been learning Rust. This is a journey that many people have undertaken, and many more will undertake in the future. This article and the many that follow it will be part of my journey, and my hope is that they can help you on yours as well. Many of these articles will follow a similar pattern, where I look at implementations in Ruby, Crystal, and Rust, and attempt to compare and contrast the solutions in those languages.

I have chosen these languages, out of all of the languages that I could use, for a few reasons. Rust is currently a popular language for Rubyists to learn, but it is a very different language from Ruby, being compiled instead of interpreted, statically type-checked instead of dynamically type-checked, and uses a novel form of memory management, borrow checking, instead of being a garbage collected language.

I have also chosen to use Crystal because it sits somewhat in the middle of these extremes. It, like Rust, is a statically type-checked, compiled language, that is also built on LLVM. However, it offers more aggressive type inference than Rust does, is garbage-collected like Ruby, and features a language syntax that is extremely Ruby-like.

As mentioned, Ruby is an interpreted, dynamically type checked, general-purpose programming language that uses automated garbage collection for memory management. It is an object-oriented programming language that also supports other programming paradigms such as procedural and functional programming. Ruby's syntax is intended to be clear and easily read, with a focus on developer productivity. I learned it in the very early 2000s, at a time when I was making my living primarily as a Perl programmer, and I have been using it for more than two decades since.

Crystal is a compiled (and interpreted, in recent versions), statically type-checked (with powerful type inference), a general-purpose programming language which uses automated garbage collection for memory management. It is an object-oriented programming language that also supports other programming paradigms such as procedural and functional programming. Its syntax is heavily inspired by Ruby, with similar levels of clarity, readability, and developer productivity. I initially learned it in 2020 by rewriting some of my Ruby projects in Crystal.

Now it is 2022, and I am learning Rust. Rust, like Crystal, is a compiled, statically type-checked, general-purpose programming language. Unlike Ruby and Crystal, it does not utilize garbage collection, and instead depends on a capability referred to as borrow checking for memory management. Like Ruby and Crystal, Rust supports multiple programming paradigms, including object-oriented programming via structs and enums.

Rust has become a major language, being used both for systems programming as well as for general programming. It is often used in place of C or C++, and it has started to see some use to replace those languages even in mature projects. It is also seeing growing use in the realms of cryptography and blockchain applications, so there is a lot of interest in learning it.

With that background, let's dive in and start learning.

Program Structure

A very simple Ruby or Crystal program will be written in a procedural manner, with lines being executed linearly from the top to the bottom of the file. Thus, one of the simplest Ruby or Crystal programs (this code will execute in both languages) would look like this:

hello_ruby.rb

puts "Hello. This is a simple Ruby/Crystal program."

To run this program with Ruby, the command line would be:

ruby hello_ruby.rb

While with Crystal, we can compile the program and then run it as follows:

crystal build hello_ruby.rb
./hello_ruby

Alternatively, one can build and run with a single command like so:

crystal run hello_ruby.rb

The Rust equivalent of puts is println!. So it might be tempting to do this:

hello_rust.rs
println! "Hello. This is a simple Rust program."

To run this, one does something similar to the first Crystal example:

rustc hello_rust.rs
./hello_rust

Rust provides excellent errors when there is something wrong with your program. When you run the above program, you will see something like this:

error: expected one of `(`, `[`, or `{`, found `"Hello. This is a simple Rust program."`
 --> hello_rust.rs:1:10
  |
1 | println! "Hello. This is a simple Rust program."
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected one of `(`, `[`, or `{`

error: aborting due to previous error

This is the first key difference. In Ruby and in Crystal, parenthesis can typically be omitted when passing arguments to methods. In Rust, they are required. So the above program can be fixed by adding the parenthesis, as indicated in the error message:

hello_rust.rs
println!("Hello. This is a simple Rust program.")

Now, when we run this program, will we get the expected output?

error[E0601]: `main` function not found in crate `hello_rust`
 --> hello_rust.rs:1:51
  |
1 | println!("Hello. This is a simple Rust program.")
  |                                                   ^ consider adding a `main` function to `hello_rust.rs`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0601`.

Take note of that last line. Running that command gives this:

No `main` function was found in a binary crate.

To fix this error, add a `main` function:

fn main() { // Your program will start here. println!("Hello world!"); }

If you don't know the basics of Rust, you can look at the Rust Book to get started.

That is pretty impressive. It shows exactly what is needed to make the program work. In this case, the program lacks a main function. In a Rust program, the main function is required if any code is to be executed. The main function will be automatically called when the program is executed.

hello_rust.rs

fn main() {
  println!("Hello. This is a simple Rust program.");
}

When you use rustc to compile the program this time, there should be no errors. Congratulations! You just wrote your first fully functional Rust program!

The base syntax of Rust is clear and fairly simple Much of it will be familiar if you have used a language like Ruby or Crystal, but the details of the language are complex and nuanced, and it is easy to get lost in those details. The Rust compiler is very helpful, and if you pay attention to the error messages that it provides, it can help you to learn the language. As you learn, you will find areas that are exactly like the languages that you already know, and you will find areas that are wildly, and sometimes confusingly different. The key is to keep learning and to keep trying, and through this article and others to follow, I will help you to do that while highlighting both those areas of similarity and those areas of difference. I am happy to have you join me on this journey.