Basic syntax

Let's have a look at some basic Rust syntax. Just like in the previous chapters, you can run these code snippets in your browser or in your local data-with-rust folder.

Variables

Variables are the smallest building blocks of your programs. They usually reference some data of some sort.

There are many ways of declaring variables in Rust. Usually, this is done by prefixing the variable name with let, const or static. A mutable variable is a variable that might change at some point, an immutable variable is a variable that can't change while the program is running.

fn main() {
    let x = 5; // defining an immutable variable

    let mut y = 6; // defining a mutable variable

    const Y2K: i32 = 2000; // const variables require a known type

    static mut POTATOES: u32 = 0; // This is a mutable static variable
}

In Rust, variables are immutable by default, meaning that their values cannot be changed after being assigned. For example, if you try to change the x value after assignment, you'll get an error.

To declare a mutable variable, you need to use the mut keyword, like it's done for y.

Now the natural question is: what is the difference between let, const and static?

  • If the variable isn't meant to be changed, there is not much difference between the three.
  • const is essentially a constant variable, it cannot be changed, it is immutable. Which would be expected from something that is constant. During compile time, it is inlined/copied to every location it is used which has some efficiency gains.
  • static is essentially a reference to a fixed location in memory, making it roughly similar to a global variable. It can be mutated but with some conditions (using unsafe code blocks).

For now, I recommend to only use let, but for the sake of completeness, this is how you would change things:

fn main() {
    let x = 5; // defining an immutable variable

    let mut y = 6; // defining a mutable variable

    y = 7; // changing a mutable variable's value

    println!("The value of y is {}", y);

    const Y2K: i32 = 2000; // const variables require a known type

    static mut POTATOES: u32 = 0; // This is a mutable static variable

    unsafe {
        POTATOES = 1;

        println!("The value of POTATOES is {}", POTATOES);
    }
}

Using unsafe is usually not recommended. If the logic of your program allows it, try to avoid unsafe.

Control flow

Now that we know how to initialise variables, we can start doing stuff with them. Most of the time, these things fall into using if/else or loops. I will not cover all the edge cases, only the ones that are relevant in 90% of the cases. For a full coverage you can refer to Rust by Example.

If/Else & match

Let's start with if/else. The syntax is similar to Python. In this case we want to check if a condition expressed as Rust code evaluates to true or not and do something in each case.

fn main() {
    let x = 5;

    if(x < 0){
        println!("Negative");
    } else if(x > 0) {
        println!("Positive");
    } else {
        println!("Zero");
    }
}

The parenthesis are not mandatory, but I strongly recommend them. Explicit is better than implicit.

Another way of rewriting the above is by using match:

fn main() {
    let x = 5;

    match x {
        n if n < 0 => println!("Negative"),
        n if n > 0 => println!("Positive"),
        _ => println!("Zero"), // Match requires all possible outcomes to be handled
    }
}

Both match and if/else are pretty similar, however, if/else statements are more appropriate for simple boolean conditions, while match statements are more appropriate for complex conditions that require pattern matching (matching types for example) and multiple possible outcomes.

Loops

Writing code involves iterating through data and this is done using loops. There are many ways to loop through things in Rust: for, while and loop.

The simplest way of a loop is doing something a number of times over:

fn main() {
    for n in 1..10 { 
        println!("N={}", n)
    }
}

The most used way however, is to iterate through a list of things:

fn main() {
    let languages = ["Python", "Rust", "C++"];

    for name in languages.iter() {
        println!("I love {}", name);
    }
}

Next up are loop & while. I like to think of loop as a "while True", they are pretty similar.

fn main() {
    let mut count = 0;

    loop {
        count += 1;

        if(count < 10){
            // continue iterating, similar to Python
            println!("Steady...");
            continue;
        }

        if(count == 10){
            println!("Bye bye!");
            // Exit the loop, similar to Python
            break;
        }
    }
}

The while version would look like this:

fn main() {
    let mut count = 0;

    while count < 10 {
        count += 1;
        println!("Steady...");

        if(count == 10){
            println!("Bye bye!");
            break;
        }
    }
}

There's much more to this, but for now we can create variables, loop through stuff and start becoming quite productive with Rust.

Next let's have a look at data types, which represents the bread and butter of data engineering.