Introduction
How to increase your fluency in a programming language? You have to read a lot of it! Very lot. But how can you read a lot, when you lack the knowledge to understand what you read.
In this article, I try to cover as many keywords and symbols as possible using Rust snippets.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
A semicolon marks the end of a statement:
let x = 3;
let y = 5;
let z = y + x;
This means that statements can span over multiple lines:
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
let
introduce a new set of variables into the current scope
let x;
x = 42;
You can write this as a single line:
let x = 42;
Variables in Rust are immutable by default, and require the mut
keyword to be made mutable.
let mut x = 42
Types can be annotated
let x: i32;
x = 42;
And again, you can write this as a single line
let x: i32 = 42;
When in doubt: Just use i32
for everything! i32
is the default in Rust
You can declare a name and initialize it later but you can not access uninitialized variables
let x;
foobar(x); // error: borrow of possibly-uninitialized variable: `x`
x = 42;
Doing it this way is completely fine:
let x;
x = 42;
foobar(x);
The underscore _
is a special symbol. It basically means that something is ignored:
// this does *nothing* because 42 is a constant
let _ = 42;
// this calls `get_thing` but throws away its result
let _ = get_thing();
Rust has tuples, which are a fixed-length collections of values of different types.
let pair = ('a', 17);
pair.0; // this is 'a'
pair.1; // this is 17
We could write, with explicit type annotation:
let pair: (char, i32) = ('a', 17);
Tuples can be destructured when doing an assignment:
let (some_char, some_int) = ('a', 17);
// now, `some_char` is 'a', and `some_int` is 17
This is very useful when a function returns a tuple:
let (left, right) = slice.split_at(middle);
You can use underscore _
to throw away a part of it:
let (_, right) = slice.split_at(middle);
To declare a function, you use fn
.
Here is a void function:
fn greet() {
println!("Hi there!");
}
And this is a function that returns a 32-bit signed integer. The arrow indicates its return type!
fn fair_dice_roll() -> i32 {
4
}
Functions can be generic:
fn foobar<T>(arg: T) {
// do something with `arg`
}
You can have multiple type parameters:
fn foobar<L, R>(left: L, right: R) {
// do something with `left` and `right`
}
Bracket pairs do declare a block. A block has its own scope:
// This prints "in", then "out"
fn main() {
let x = "out";
{
// this is a different `x`
let x = "in";
println!("{}", x);
}
println!("{}", x);
}
Keep in mind that Blocks are also expressions. This means they evaluate to a value.
// this:
let x = 42;
// is equivalent to this:
let x = { 42 };
There can be multiple statements inside a block:
let x = {
let y = 1; // first statement
let z = 2; // second statement
y + z // this is the *tail* - what the whole block will evaluate to
};
When you omit the semicolon at the end of a function, it is the same as returning:
fn fair_dice_roll() -> i32 {
return 4;
}
fn fair_dice_roll() -> i32 {
4
}
if
conditions are expressions too:
fn fair_dice_roll() -> i32 {
if feeling_lucky {
6
} else {
4
}
}
And a match
is also an expression and not a statement!
fn fair_dice_roll() -> i32 {
match feeling_lucky {
true => 6,
false => 4,
}
}
Dots .
are used to access fields of a value:
let a = (10, 20);
a.0; // this is 10
let amos = get_some_struct();
amos.nickname; // this is "fasterthanlime"
Or to call a method on a value:
let nick = "fasterthanlime";
nick.len(); // this is 14
The double-colon, ::
, is similar, but it operates on namespaces.
In this example, std is a crate (~ a library), cmp is a module (~ a source file), and min is a function:
let least = std::cmp::min(3, 8); // this is 3
A Struct is declared with the struct keyword
struct Number {
odd: bool,
value: i32,
}
And they can be initialized using literals
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
You can declare methods on your own types:
impl Number {
fn is_strictly_positive(self) -> bool {
self.value > 0
}
}
Structs can be generic too:
struct Pair<T> {
a: T,
b: T,
}
fn print_type_name<T>(_val: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let p1 = Pair { a: 3, b: 9 };
let p2 = Pair { a: true, b: false };
print_type_name(&p1); // prints "Pair<i32>"
print_type_name(&p2); // prints "Pair<bool>"
}
A vector is a container that stores the values like an array, but it has more advantages than an array data structure. A vector can increase it's size dynamically during runtime.
It is provided by the standard library and is a generic.
fn main() {
let mut v1 = Vec::new();
v1.push(1);
let mut v2 = Vec::new();
v2.push(false);
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
While we talked about Vectors, let's check the vec!
macro:
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
All types of name!()
, name![]
or name!{}
are invoking a macro. Macros just expand to regular code.
You already know a famous macro: println!
fn main() {
println!("{}", "Hello there!");
}
This macro expands to:
fn main() {
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}
Rust provides a loop keyword to indicate an infinite loop.
loop {
count += 1;
if count == 3 {
println!("three");
// Skip the rest of this iteration
continue;
}
println!("{}", count);
if count == 5 {
println!("OK, that's enough");
// Exit this loop
break;
}
}
Or you can use the while
loop. As the name stated, this loops runs while a condition is true.
fn main() {
// A counter variable
let mut n = 1;
// Loop while `n` is less than 101
while n < 101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
// Increment counter
n += 1;
}
}
Anything that is iterable can be used in a for in
loop. One of the easiest ways to create an iterator is to use the range notation a..b
.
fn main() {
// `n` will take the values: 1, 2, ..., 100 in each iteration
for n in 1..101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
}
}
But for in
it also works with a Vec
:
fn main() {
for i in vec![52, 49, 21] {
println!("I like the number {}", i);
}
}
Or a slice:
fn main() {
for i in &[52, 49, 21] {
println!("I like the number {}", i);
}
}
Or an actual iterator
:
fn main() {
// note: `&str` also has a `.bytes()` iterator.
// Rust's `char` type is a "Unicode scalar value"
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
You can catch the failure of some parts of your program instead of calling panic!
. This can be done using the Option
enum.
The Option<T>
enum has two variants:
None
, to indicate failure or lack of value, andSome(value)
, a tuple struct that wraps a value with typeT
.
enum Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
// enums variants can be used in patterns:
match self {
Self::Some(t) => t,
Self::None => panic!(".unwrap() called on a None option"),
}
}
}
use self::Option::{None, Some};
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // this is fine
let o2: Option<i32> = None;
o2.unwrap(); // this panics!
}
Sometimes it is important to display why an operation failed. To do this you can use the Result
enum.
The Result<T, E>
enum has two variants:
- Ok(value) which indicates that the operation succeeded
- Err(why), which indicates that the operation failed, and wraps why, which explains the cause of the failure.
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => println!("Problem opening the file: {:?}", error),
};
}
Further reading
I hope, I could give a got overview about Rust in under 10 mins. Of course, this is just the beginning of a wonderful journey in the word of Rust. For more Rust material, check out the following links: