Rust is an expression-based language. Unlike statements:
For example:
fn main() {
let x = 1;
// The {} block is an expression. The entire line is a statement.
let y = {
let x = 7; // Different x
x + 1 // Notice how there is no semi-colon here.
};
println!("y = {}", y);
}
For loops are used to iterate over collections. Don't worry, a normal C++ for loop is still applicable. You just need to make it more like Python and iterate through some numbers!
fn main() {
let arr = [1, 2, 3, 4, 5];
// Iterate through a collection
for element in arr.iter() {
println!("The value is: {}", element);
}
// Iterate through a range of values
for number in 1..6 {
println!("The value is: {}", number);
}
// Iterate through a range, backwards
for number in (1..6).rev() {
println!("The value is: {}", number);
}
// Inclusive end
for _ in 1..=9 {}
// Use vars
let a = 0;
let b = 10;
for i in a..b {
println!("{}", i) # println! requires a string literal, not just a variable
}
}
Rust is a standard if
, else if
, else
language, however, only boolean values are allowed for the conditional. Integers will cause a mismatched types
error.
fn main() {
let number = 12;
// Standard if, else if, else expression
if number % 3 == 0 {
println!("Number is divisible by 3");
} else if number % 2 == 0 {
println!("Number is divisible by 2");
} else {
println!("Math hard, I gave up.");
}
// Using "if" in a let statement (because it's an expression, not a statement)
let number = if number > 0 { number } else { 0 }; // Return types must match
println!("number is now: {}", number);
}
If there are many else if
expressions, Rust recommends using match
instead.
The if let
is the little sibling to match
. if let
allows you to match only one arm of a match
expression. The if let
expression takes a pattern on the left hand side of an EQUAL SIGN and an expression on the right hand side of an EQUAL SIGN.
if let
can also be combined with normal if-else
statements.
Example:
#[derive(Debug)]
enum Breed {
Husky,
Poodle,
Lab,
// Etc.
}
enum Animal {
Cat,
Giraffe,
Dog(Breed), // This Animal::Dog type contains Breed data
}
fn main() {
// Example A
let some_i32 = Some(17i32); // Make 17 an i32 type
if let Some(3) = some_i32 { // Notice the "=" sign
println!("Three!");
} else {
println!("The number was not three :(");
}
// Example B
let dog = Animal::Dog(Breed::Husky);
if let Animal::Dog(breed) = dog {
println!("We found a {:?} dog!", breed);
}
}
Rust has three kinds of loops: loop
, while
, and for
There also exists the break
statement to break out of a loop and the continue
statement to immediately go to the next iteration of the loop.
Values can be returned from loops. For example:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 5 {
break counter * 7;
}
};
println!("result = {}", result);
}
The loop
loop creates an infinite loop.
fn main() {
loop {
println!("Never ending!");
}
}
match
is like a switch-case in most other languages. It consists of the match
keyword followed by an expression (generally a single variable). In the curly brackets comes the arms. Each arm consists of a pattern and some code.
Matches are exhaustive. Every case for an enum must be handled. match
does provide a catch all via the _
placeholder.
A |
can be used to match multiple patterns.
Each arm of a match expression must return the same type (the "never type" is an exception).
Example:
#[derive(Debug)]
enum Breed {
Husky,
Poodle,
Lab,
// Etc.
}
enum Animal {
Cat,
Giraffe,
Dog(Breed), // This Animal::Dog type contains Breed data
Fox,
Hamster,
Snake,
Daltie,
}
fn sound(animal: Animal) -> String {
match animal {
Animal::Cat => String::from("Meow"),
Animal::Giraffe => String::from("Hello good Sir"),
Animal::Dog(breed) => { // Function like arm block
println!("I am a {:?}", breed);
String::from("Woof")
},
Animal::Snake | Animal::Daltie => String::from("I'm a sssnake!"),
_ => String::from("??"), // Catches the Fox and Hamster cases
}
}
fn main() {
let husky = Animal::Dog(Breed::Husky);
println!("{}", sound(husky));
// Match range
let x = 2;
match x {
1..=7 => println!("Small num"),
_ => println!("Invalid"),
}
}
Structs, enums, tuples, and references can be destructed to their individual parts.
struct Point{ x: u32, y: u32 }
fn main() {
let p = Point { x: 0, y: 7 };
let Point{ x: a, y } = p; // a and y are now valid variables in this scope
// Match destructured values
match p {
Point { x, y: 0 } => println!("On the x-axis at {}", x),
Point { x: 0, y } => println!("On the y-axis at {}", y),
Point { x, y } => println!("Somewhere, over a rainbow? ({}, {})", x, y),
}
}
struct Point{ x: u32, y: u32 , z: u32}
fn main() {
let p = Point { x: 0, y: 7, z: 14 };
// Ignore everything other than y
match p {
Point { y, .. } => println!("Only care about y: {}", y), // 7
}
let numbers = (1, 2, 3, 4, 5);
// Only match first and last values
match numbers {
(first, .., last) => println!("{}, {}", first, last), // 1, 5
}
}
fn main() {
let num = Some(7);
match num {
Some(x) if x < 5 => println!("Less than 5"),
// Match expressions much be exhaustive, so not including
// this line would cause an complication error
Some(x) => println!("{}", x),
None => (),
}
}
@
allows you to both test a pattern and bind a variable.
enum Height {
Meters { m: u32 },
}
fn main() {
let tall = Height::Meters{ m: 7 };
match tall {
Height::Meters {
m: meters_var @ 0..=9,
} => println!("You're only {} meters tall! You short!", meters_var),
Height::Meters { m: 10..=99 } => {
println!("You fit") // variable m is not available, need @ if want variable binding
}
Height::Meters { m } => println!("{} is so tall", m), // Since there is no testing, m is available
}
}
Conditional looping!
fn main() {
let mut number = 0;
while number != 5 {
println!("{}", number);
number += 1;
}
}
Similar to if let
, while let
allows you to loop until the "if let" is false.
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop {
println!("{}", top);
}