created on 2020-08-29, edited on 2021-06-13

#computer-science #rust

Learning Rust

Here is a list of stuff that I find interesting and important while trying to learn the Rust Language.

Variables

Variables are often calleed bindings in Rust. It makes sense since by default variables are immutable in Rust. Any variable that goes out of scope is dropped. Dropping means releasing the resources that are tied(bound) to that variable. Only one thing can own a piece of data at a time in Rust.

A small note about lifetimes

struct MyStruct<'lifetime>(&'lifetime str);

This annotation means that the lifetime of MyStruct can not outlive the reference that holds in its fields.

As of April 1st 2022, I understand the mut keyword better.

fn my_function(mut x: i32)

In this case mut modifies the variable x, not the variable that is passed. The passed variable does not need to be defined as mut.

let v0 = vec![0, 1, 2];
// v0.push(3); // error
let mut v1 = v0;
v1.push(3);

This is another interesting case. v0 is not defined as mut and when I try to push into it, the compiler throws an error but you can bind v0 to a mutable variable, in this case v1.

As you know you can only have one mutable reference at a time. The code below does not compile because at the execution of line 5 there are 2 mutable references to the same variable x. But if you switch line 4 and 5 it compiles because mutable reference y is not used at that point.

fn main() {
    let mut x = 100;
    let y = &mut x;
    let z = &mut x;
    *y += 100;
    *z += 1000;
    assert_eq!(x, 1200);
}

into_iter, iter and iter_mut

Arrays

An array can be defined in two ways:

Option

Option enum is used when the absence of a value is a possibility, known as null reference in some other languages. The two examples below are identical, a good explanation for if let

// 1
let some_number: Option = Some(7);
match some_number {
    Some(7) => println!("That's my lucky number!"),
    _ => {},
}
// 2
let some_number: Option = Some(7);
if let Some(7) = some_number {
    println!("That's my lucky number!");
}

Traits

Two ways of having trait bounds

// 1
fn fn_one(value: &impl Trait) { ... }
// 2
fn fn_two<T: Trait>(value: &T) { ... }

Iterator trait looks like this in Rust standard library One interesting thing is 'type Item'. It means every implementation of Iterator should return an associated type Item.

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

(?) syntax sugar

(?) is used to propagate errors. The two functions below are equivalent.

fn function_1() -> Result(Success, Failure) {
	match operation_that_might_fail() {
		Ok(success) => success,
		Err(failure) => return Err(failure),
	}
}

fn function_2() -> Result(Success, Failuer) {
	operation_that_might_fail()?
}

Fun topic about string literals https://doc.rust-lang.org/reference/tokens.html#raw-string-literals

Conditional Compilation for Debug and Release Builds

Here is a way to conditionally compile Rust code for Debug and Release builds

#[cfg(debug_assertions)]
fn example() {
    println!("Debugging enabled");
}

#[cfg(not(debug_assertions))]
fn example() {
    println!("Debugging disabled");
}

fn main() {
    if cfg!(debug_assertions) {
        println!("Debugging enabled");
    } else {
        println!("Debugging disabled");
    }

    #[cfg(debug_assertions)]
    println!("Debugging enabled");

    #[cfg(not(debug_assertions))]
    println!("Debugging disabled");

    example();
}