home

posts

notes

rss

Learning Rust

I started experimenting with the Rust programming language by reading “the book”, trying some basic things and loosely following along with the examples. Although I’ve only gone through the first three chapters — stopping after the common programming concepts and leaving the idea of ownership for later — I’m still planning to write down what I found interesting about the language compared to the ones I use more often (mainly Python and Julia).

Nothing interesting will be written here for anyone familiar with Rust, but I’m growing fond of the idea behind “blogumentation” for self-documentation. In this case, writing things down that I found interesting will help me remember them:

Terminating statements with ;

Like multiple other languages, Rust uses the semicolon after statements. I wondered why this is so often the convention, and found a blog post by Nicole Tietz speculating why that is the case.

Cargo and initialising Git repositories

Using Cargo — which also initialises a new Git repository by default and creates the project structure when you begin a new project — seems like a great workflow.

“Cargo expects your source files to live inside the src directory. The top-level project directory is just for README files, license information, configuration files, and anything else not related to your code. Using Cargo helps you organise your projects. There’s a place for everything, and everything is in its place.”

In general, I think it’s helpful that it initialises and enforces this organisation. I tend to ignore conventions when quickly whipping something up in Python, which gets messy quickly.

Shadowing of immutable variables

Variables are immutable by default, but I was initially confused as to why you would allow shadowing for immutable variables. I did not immediately see any scenario where it would make sense to shadow an immutable variable. This will probably make more sense once I see some examples in practice. Another thing about shadowing that I found a bit weird is the example below:

let spaces = " ";
let spaces = spaces.len();

“The first spaces variable is a string type and the second spaces variable is a number type. Shadowing thus spares us from having to come up with different names, such as spaces_str and spaces_num; instead, we can reuse the simpler spaces name.”

This feels like a fast way for things to get messy, shadowing the same variable in different scopes but changing the type (and thus the meaning) of the variable, but keeping the name. spaces_str and spaces_num don’t sound so bad to me, as the names at least convey meaning.

Visual separator for integer literals

“Number literals can also use _ as a visual separator to make the number easier to read, such as 1_000, which will have the same value as if you had specified 1000.”

Through looking up other languages that provide a visual separator for integer literals, I stumbled upon this GitHub issue, which contains some interesting discussion on separators.

Other things that I like so far, at first glance

A lot of these might seem very standard, but as Python is my daily driver, it’s nice to try a language that has:

  • Integer division truncating toward zero to the nearest integer:
let truncated = -5 / 3; // Results in -1
  • The terminology “destructuring” for breaking up a single tuple into parts (which Python also has, but first time that I encounter this terminology):
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
  • The distinction between arrays and vectors, and the initialisation of an array using this notation:
let a = [3; 5]; // same as writing let a = [3, 3, 3, 3, 3];
  • The fact that you must declare the type of each parameter in function signatures:
fn main() {
print_labeled_measurement(5, 'h');
}
 
fn print_labeled_measurement(value: i32, unit_label: char) { //
println!("The measurement is: {value}{unit_label}");
}
  • The distinction between acting and returning through statements and expressions (and that expressions don’t have ending semicolons). I already see myself making mistakes by turning expressions into statements — not letting it return a value — by accidentally adding a semicolon. Luckily, the thrown errors are very readable, so far:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
  • The fact that conditions must explicitly be booleans:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected `bool`, found integer
 
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` (bin "branches") due to 1 previous error
  • One-line assignment using if, but enforcing that the result of each arm must be of the same type:
let number = if condition { 5 } else { 6 };
  • The loop keyword and loop labels (probably used sparingly as there are while and for keywords as well):
fn main() {
loop {
println!("again!");
}
}

Looking forward to learning more about Rust!