Rust cheatsheet examples part_1
Tags: rust, cheatsheet, tutorial
Published: 2024-02-15 13:19:44

### Basic Types & Variables

##### Boolean
- `bool` - Boolean (`true` or `false`)

##### Unsigned integers
- `u8`, `u16`, `u32`, `u64`, `u128`

##### Signed integers
- `i8`, `i16`, `i32`, `i64`, `i128`

##### Floating point numbers
- `f32`, `f64`

##### Platform specific integers
- `usize` - Unsigned integer. Same number of bits as the platform's pointer type.
- `isize` - Signed integer. Same number of bits as the platform's pointer type.

##### Characters and Strings
- `char` - Unicode scalar value
- `&str` - String slice
- `String` - Owned string

##### Null / Nil 
Rust doesn’t have the `null` feature that many other languages have. _Null_ is a value that means there is no value there. Rust does have an enum that can encode the concept of a value being present or absent. This enum is `Option<T>`, and it is [defined by the standard library](https://doc.rust-lang.org/std/option/enum.Option.html) as follows:

```rust
enum Option<T> {     
  Some(T), 
  None
}
```

You can use `Some` and `None` directly without the `Option::` prefix. The `Option<T>` enum is still just a regular enum, and `Some(T)` and `None` are still variants of type `Option<T>`.

##### Tuple
```rust
let coordinates = (82, 64);
let score = ("Team A", 12);
```

##### Array & Slice
Slices are similar to arrays, but their length is not known at compile time. Instead, a slice is a two-word object; the first word is a pointer to the data, the second word is the length of the slice
```rust
let nums = [1, 2, 3, 4, 5];
let zeros = [0; 4]; // [0, 0, 0, 0]
let slice = &nums[1..4];

```

##### Hashmap

```rust
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Team1"), 900);
scores.entry("Team2".to_owned()).or_insert(250);
```

##### Struct

```rust
struct Person {
    name: String,
    is_active: bool,
}

let person1 = Person {
    name: String::from("alex"),
    is_active: false,
};

struct Point(i32, i32, i32);
let white = Point(255, 255, 255);
```

##### Enum

```rust
enum Action {
    Stop,
    Go { x: i32, y: i32 },
    Yell(String),
    ChangeColor(i32, i32, i32),
}

let act1 = Action::Stop;
let act2 = Action::Go { x: 10, y: 20 };
let act3 = Action::Yell("Hello".to_owned());
let act4 = Action::ChangeColor(255, 255, 0);
```

##### Constant

```rust
const MAX_SCORE: u32 = 10000;
```

##### Static variable
```rust
static APP_VERSION: u32 = 1;
static mut HIT_COUNT: u32 = 0;
```

##### Mutability
```rust
let mut val = 5; 
val = 7;
```

##### Shadowing
```
let val = 5; 
let val = val + 2;
```

##### Type alias `type Score = u64;`

### Control Flow

#### if and if-let

```rust
let maybe_num = Some(7);

if maybe_num.is_some() {
    println!("number is: {}", maybe_num.unwrap());
}

// `if let` allows various failure options to be specified:
if let Some(i) = letter {
	println!("Matched {:?}!", i);
} else {
	// Destructure failed. Change to the failure case.
	println!("Didn't match a number. Let's go with a letter!");
}
```

#### let-else
With `let`-`else`, a refutable pattern can match and bind variables in the surrounding scope like a normal `let`, or else diverge (e.g. `break`, `return`, `panic!`) when the pattern doesn't match. 
The scope of name bindings is the main thing that makes this different from `match` or `if let`-`else` expressions.
```rust
let (Some(count_str), Some(item)) = (it.next(), it.next()) else { 
  panic!("Can't segment count item pair: '{s}'");
}; 

let Ok(count) = u64::from_str(count_str) else { 
    panic!("Can't parse integer: '{count_str}'"); 
};
```
#### while-let
`while let` can make awkward `match` sequences more tolerable.
```rust
let mut optional = Some(0);

// This reads: "while `let` destructures `optional` into
// `Some(i)`, evaluate the block (`{}`). Else `break`.
while let Some(i) = optional {
    if i > 9 {
        println!("Greater than 9, quit!");
        optional = None;
    } else {
        println!("`i` is `{:?}`. Try again.", i);
        optional = Some(i + 1);
    } // Doesn't require explicitly handling the failing case.
}
```
#### Loops
```rust
let mut counter = 0;
loop {
    counter += 1;
    if counter == 3 {
        break; // Exit loop
    }
}

let mut count = 0;
let result = loop {
    count += 1;
    if count == 5 {
        break count; // Return value from loop
    }
};

let mut num = 0;
while num < 50 {
    num += 1;
}
```

#### Nested loops & labels

```rust
'outer: loop {
    'inner: loop {
        break; // This breaks the inner loop
        break 'outer; // This breaks the outer loop
    }
}
```

#### for loop

```rust
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);
	}
}
```

`for` loop takes ownership of `v1_iter` and makes it mutable behind the scenes.
```rust
let v1 = vec![1, 2, 3]; 
let v1_iter = v1.iter(); 

for val in v1_iter { 
  println!("Got: {}", val); 
}
```

### Pattern Matching ❤️
Rust provides powerful pattern matching capabilities through the `match` expression and `if let` syntax, which can destructure, test, and compare values in a clean and concise way.
```rust 
let x = 3;
match x {  
	1 => println!("one"),  
	2 | 3 => println!("two or three"),  // multiple values
	4..=9 => println!("within range"), // matching ranges  
	x => println!("{}", x),  // matching named variables  
	_ => println!("default Case") // default case (ignores value)  
}

// Another example:
let option_value = Some(5); 

match option_value { 
  Some(value) => println!("The value is: {}", value), 
  None => println!("The value is missing"), 
} 
if let Some(value) = option_value { println!("The value is: {}", value); }

```
#### Destructuring
A `match` block can be used to destructure Tuples, Arrays/Slices, Enums, Pointers, Structs.
```rust
enum Color {
    Red,
    Blue,
    Green,
    RGB(u32, u32, u32),
    CMYK(u32, u32, u32, u32),
}

fn main() {
    let color = Color::RGB(122, 17, 40);

    println!("What color is it?");
    match color {
        Color::Red   => println!("The color is Red!"),
        Color::Blue  => println!("The color is Blue!"),
        Color::Green => println!("The color is Green!"),
        Color::RGB(r, g, b) =>
            println!("Red: {}, green: {}, and blue: {}!", r, g, b),
        Color::CMYK(c, m, y, k) =>
            println!("Cyan: {}, magenta: {}, yellow: {}, key (black): {}!",
                c, m, y, k),
        ...
    }
}


Part 2 is coming soon.

let array = [1, -2, 6];

match array {
	// Binds the second and the third elements to the respective variables
	[0, second, third] =>
		println!("array[0] = 0, array[1] = {}, array[2] = {}", second, third),

	// Single values can be ignored with _
	[1, _, third] => println!(
		"array[0] = 1, array[2] = {} and array[1] was ignored",
		third
	),
	...
}
```
More fun stuff - [Destructuring examples](https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring.html)

#### Binding

Indirectly accessing a variable makes it impossible to branch and use that variable without re-binding. `match` provides the `@` sigil for binding values to names:
```rust
fn some_number() -> Option<u32> {
    Some(42)
}

fn main() {
    match some_number() {
        // Got `Some` variant, match if its value, bound to `n`,
        // is equal to 42.
        Some(n @ 42) => println!("The Answer: {}!", n),
        // Match any other number.
        Some(n)      => println!("Not interesting... {}", n),
        // Match anything else (`None` variant).
        _            => (),
    }
}
```

Part 2 is coming soon!