I recently decided to dip my toe into learning Rust. This is the first of a series articles recording my journey to understanding Rust. It isn’t intended to replace other references, but I hope to point out a few things from the point of view of someone who worked with C/C++ ages ago, Java an age ago, and current mostly works in Ruby. Some of those might help others from similar backgrounds or if they just happen to get hung up on some of the same things I do.
It’s worth nothing that my newness to the language may mean I misuse some terms from time to time. Less likely but definitely possible is that I may outright have a concept wrong. The plan is to come back and provide updates, but I can’t guarantee it. A point will also be made of calling out any code that is especially production-worthy. Otherwise, assume all of this is “example only”. Learn the concepts. Don’t copy the code.
Let me know if you find an error. Links to some social media accounts are on the About page. I’m on Mastodon the most lately.
Getting Up and Running
Though I haven’t updated the notes here in quite a while, I do still use asdf. So in my case, getting Rust installed was a matter of:
asdf plugin add rust
asdf list all rust
asdf install rust 1.67.0 # latest per the previous step
asdf global rust 1.67.0 # just so I have a default
rustc --version
#--> rustc 1.67.0 (fc594f156 2023-01-24)
Rustlings
I was quite happy to find an official prebuilt self-study course in the form of Rustlings. It somewhat gamifies learning the basics (and maybe slightly beyond) of Rust by providing a set of exercises in the form of code that doesn’t quite work. Many files fail on the compilation step. Others have assertios that fail. Some have both. The goal is to get each one working and to then move on to the next. The excercises are broken into sections so that there are some natural points for taking breaks.
I’ll probably have a few “aha” moments, but mostly I’m learning from it and the references it mentions. So if it works for you, feel free to use Rustlings and skip the rest of this series. Or read along here before or after doing the related exercises. I won’t go in exactly the same order, but I will cover a lot of the topics.
Installation
Following their README, I chose to do a “manual” installation and ran:
clone -b 5.3.0 --depth 1 https://github.com/rust-lang/rustlings
cd rustlings
cargo install --force --path .
asdf reshim rust 1.67.0
That last line is not from their README. That is needed because I use asdf and the cargo install
command installs the resulting executable in the Rust bin
location. The executable will be in ~/.asdf/installs/rust/1.67.0/bin/
(if your setup is the same as mine), but not reachable. In fact, asdf won’t know about it at all, so you’ll likely get “command not found” rather than asdf’s usual, “No version is set for command rustlings”. The asdf reshim
updates the shim to include rustlings
and any other new executables in that bin
directory.
First Observations
I’m writing this from the perspective of someone who has done a good bit of programming over the years. I’ll try to explain most concepts that are Rust-specific, but I don’t expect this series to be a good way to learn Rust without any prior programming experience. For example, I’ll start by telling you that functions in Rust are defined using the fn
keyword, like so:
fn function_name() {
// do stuff
}
But I won’t really cover what a function is. Fair enough? Let’s go then.
Variables and Values
Okay, we’re starting pretty basic, but I have my reasons. Variables are more-or-less what you’d expect from other languages. They are names provided for being able to access something. In Rust, we let
a name become a variable.
let x = 1;
Values are concrete things variables provide access to. Above, the literal 1
is the value. Also of note, variables are immutable by default. To make them mutable, we must use the mut
keyword in the declaration.
|
|
If line 8 above is commented out, everything will work as expected. The output will be:
x = 1
y = 2
x = 1
y = 1
Semicolons, Statements, and Expressions
Like Java and C/C++, but unlike Ruby, Rust requires statements to have a clear terminator. But not everything in Rust is a statement. There are also expressions. These two things, statements and expressions, have very specific meanings. The key is that a statement does not have a result; an expression does. Let’s look at some examples:
// statement terminated by semicolon - does not return a value
let x = 6;
// `let` makes this a statement causing a complilcation error
// because of the missing semicolon
let y = 5
Because a let
must be a statement, it also cannot be used in a place where a value is required. The following won’t compile even if we add a semicolon after the 5
:
let x = (let z = 5);
The error will be something like “expected expression, found let
statement”. You’ll actually get a few errors and warnings because technically there are a few things wrong with that attempted statement. Rust compilation messages are pretty verbose. They are often pretty helpful as well. I have heard they start to get confusing but haven’t run across that yet.
In Ruby, the last executed line will be returned. The same is true of Rust but only if that last line is an expression. What does that mean? Well, a function that returns a value can be a statement if it doesn’t return anything or if we just end the call with a semicolon. (Just ignore the ->i32
for now. More on that later.)
fn foo() -> i32 {
bar(); // used as a statement
bar() // used as an expression
}
fn bar() -> i32 {
42
}
Rust also has a return
keyword that allows for returning a value early. It can be the last thing in a function as well, though. Oddly, it can be written with or without an ending semicolon. The compiler doesn’t seem to care.
fn foo() -> i32 {
if its_wednesday() {
return bar() // works fine without a semicolon
} else {
return baz(); // works with one as well
}
}
Parentheses
Function calls in Rust require parentheses, where in Ruby they are optional. In Rust it causes a compilation error to try to call a function without them, even if the function takes no parameters.
This is not true for control flow statements like if
. The second of these will cause a compilation warning:
if (a > b) {
// do things
}
The warning says, “unnecessary parentheses around if
condition”. See, I’m skipping some concepts now, I didn’t try to explain what if
does.
Conclusion
That’s a pretty good start. Now we know:
- how to get Rust installed (if you’re using asdf)
- what Rustlings is
- some language basics
- variables and values
- statements and expressions
- syntax rules around parentheses
Next time we’ll learn a bit about types and what that -> i32
was really doing.