Introduction to Rust Basics - 1

Introduction to Rust Basics - 1

Rust is a system programming language that emphasizes safety and performance. If you're new to Rust, understanding its core concepts such as variables, data types, functions, and control flow is essential. In this post, we will explore these foundational elements of Rust, providing examples to help you get started.

Variables in Rust

By default, variables in Rust are immutable(cannot change them after declaration). To make a variable mutable, you need to use the mut keyword.

Variable Declaration

Here's how you can declare an immutable variable:

let x: i8 = 5;
println!("The value of x is: {x}");
// x = 6; // This line will cause a compile-time error since `x` is immutable.

To declare a mutable variable, use mut:

let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");

Constants

Constants are always immutable, and you cannot use the mut keyword to make them mutable. Constants are defined using the const keyword and must have their type specified.

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Shadowing

Shadowing allows you to declare a new variable with the same name as a previous variable. This can be useful for transforming a value without needing to create new variable names.

let x = 5;
let x = x + 1; // This does not throw an error; it creates a new variable `x`.
{
    let x = x * 2;
    println!("The value of x in the inner scope is: {x}"); // prints 12
}
println!("The value of x is: {x}"); // prints 6

Shadowing can also change the variable's type:

let spaces = "   "; // String
let spaces = spaces.len(); // Number

In contrast, using mut to change the type of a variable results in an error:

let mut spaces = "   ";
// spaces = spaces.len(); // This will cause a compile-time error

Data Types

Rust has several basic data types that fall into two categories: scalar and compound.

Scalar Data Types

Scalar types represent a single value. Rust has four primary scalar types:

  1. Integers

  2. Floating-point numbers

  3. Booleans

  4. Characters

Integer Types

Integer types can be signed (i.e., they can be negative) or unsigned (i.e., they cannot be negative). Here are some examples:

  • i8, u8 - 8-bit

  • i16, u16 - 16-bit

  • i32, u32 - 32-bit

  • i64, u64 - 64-bit

  • i128, u128 - 128-bit

  • isize, usize - architecture-dependent

Integer literals can be written in various forms:

  • Decimal: 98_222

  • Hex: 0xff

  • Octal: 0o77

  • Binary: 0b1111_0000

  • Byte (u8 only): b'A'

Floating-Point Types

Rust has two floating-point types: f32 and f64:

let x = 2.0; // f64
let y: f32 = 3.0; // f32

Booleans and Characters

Booleans can be either true or false:

let f: bool = false;

Characters are specified using single quotes:

let c = 'z';
let z: char = 'ℤ'; // With explicit type annotation
let heart_eyed_cat = '😻';

Numeric Operations

Rust supports standard numeric operations:

// Addition
let sum = 5 + 10;

// Subtraction
let difference = 95.5 - 4.3;

// Multiplication
let product = 4 * 30;

// Division
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // Results in -1

// Remainder
let remainder = 43 % 5;

Compound Types

Compound types group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

Tuples

Tuples group different types of values:

let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");

let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;

Arrays

Arrays group multiple values of the same type:

let a = [1, 2, 3, 4, 5];

let a: [i32; 5] = [1, 2, 3, 4, 5];

let a = [3; 5]; // [3, 3, 3, 3, 3]

let first = a[0];
let second = a[1];

Accessing an array out of bounds will cause a runtime error.

Functions

Functions in Rust are defined using the fn keyword:

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Statements do not return values, so they cannot be assigned to variables:

fn main() {
    let x = (let y = 6); // This will cause a compile-time error
}

To return a value from a block, ensure the last expression does not have a semicolon:

fn main() {
    let y = {
        let x = 3;
        x + 1 // No semicolon here
    };

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

Control Flow

Rust provides several ways to control the flow of your program.

if Expressions

The if expression allows you to branch your code:

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

Loops

Rust has three kinds of loops: loop, while, and for.

loop

The loop keyword creates an infinite loop:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2; // break exits loop and returns the value
        }
    };

    println!("The result is {result}"); // 20
}

while

The while loop runs while a condition is true:

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

for

The for loop iterates over a collection:

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

fn main2() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

Conclusion

These are the basics of Rust, covering variables, data types, functions, and control flow. Understanding these concepts will provide a strong foundation as you continue to explore the powerful features of Rust. Happy coding!