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
andspaces_num
; instead, we can reuse the simplerspaces
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 arewhile
andfor
keywords as well):
fn main() { loop { println!("again!"); }}
Looking forward to learning more about Rust!