Skip to content

XPEHO/xpelab_rust

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XPELAB RUST

This project was created to be used as a learning support for those who want to try Rust lang.

Getting Started

To get started with Rust, you need to have Rust installed on your machine.

Installation

Please refer to the official Rust installation guide: https://rust-lang.org/learn/get-started/

IDE Setup

We recommend using Visual Studio Code with the Rust Analyzer extension for a better development experience.

https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer

Variables

Let's declare

Use the let keyword to declare variables.

let age = 39;
let pi = 3.14;
let name = "XPELAB";
let is_active = true;

Types

In Rust, variable types can be explicitly declared by annotating them after the variable name.

let age: i32 = 39;
let pi: f64 = 3.14;
let name: &str = "XPELAB";
let is_active: bool = true;

Enums

Enums are a way to define a type that can be one of several variants.

enum Direction {
    Up,
    Down,
    Left,
    Right,
}
let dir: Direction = Direction::Up;

Enums can have associated data. Data of each variant can be different.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
let msg: Message = Message::Move { x: 10, y: 20 };

Sequences

Sequences are collections of values of the same type.

let numbers: [i32; 5] = [1, 2, 3, 4, 5]; // Array
let names: Vec<&str> = vec!["Alice", "Bob", "Charlie"]; // Vector

Slices

Slices are a view into a contiguous sequence of elements in a collection.

let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &numbers[1..4]; // Slice containing elements 2, 3, 4

Include slice boundary examples:

let slice_from_start: &[i32] = &numbers[..3]; // Slice containing elements 1, 2, 3
let slice_to_end: &[i32] = &numbers[2..]; // Slice containing elements 3, 4, 5
let full_slice: &[i32] = &numbers[..]; // Slice containing all elements
let full_slice_with_equal: &[i32] = &numbers[0..=4]; // Slice containing all elements using =

Tuples

Tuples are a way to group multiple values into a single compound value.

let person: (&str, i32, bool) = ("Alice", 30, true);
let (name, age, is_active) = person; // Destructuring

Mutability

By default, variables in Rust are immutable. To make a variable mutable, use the mut keyword.

let mut age = 39;
age = 40; // This is allowed because 'age' is mutable

Ownership & Borrowing

Ownership is a key feature in Rust that ensures memory safety without a garbage collector. Each value in Rust has a single owner, and when the owner goes out of scope, the value is dropped.

Ownership

When you assign a value to a new variable, the ownership of that value is moved to the new variable.

let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2, s1 is no longer valid

Borrowing

Borrowing allows you to reference a value without taking ownership of it. You can borrow a value using references.

let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrowing s1

Different way to declare variables and references

let age: i32 = 30; // Immutable variable
let mut score: i32 = 100; // Mutable variable
let age_reference: &i32 = &age; // Immutable reference
let mutable_score_reference: &mut i32 = &mut score; // Mutable reference

Control Flow

Control flow in Rust is managed using conditional statements and loops.

If-Else

let number = 5;
if number < 10 {
    println!("The number is less than 10");
} else if number == 10 {
    println!("The number is equal to 10");
} else {
    println!("The number is greater than 10");
}

Loop

let mut count = 0;
loop {
    count += 1;
    if count == 5 {
        break;
    }
}

While

let mut number = 3;
while number != 0 {
    println!("{}!", number);
    number -= 1;
}
println!("Liftoff!");

For

let a = [10, 20, 30, 40, 50];
for element in a.iter() {
    println!("The value is: {}", element);
}

Match

let number = 13;
match number {
    1 => println!("One"),
    2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
    13..=19 => println!("A teen"),
    _ => println!("A number"),
}

Functions

Declaration

Functions in Rust are declared using the fn keyword.

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

Parameters & Return

Functions can take parameters and return values.

fn add(a: i32, b: i32) -> i32 {
    a + b // Equivalent to return a + b;
}

Options

Options in Rust are used to represent a value that can either be present (Some) or absent (None).

fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

Result

Results in Rust are used for error handling. A Result can be either Ok (indicating success) or Err (indicating failure).

fn read_file(filename: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(filename)
}

fn main() {
    match read_file("hello.txt") {
        Ok(contents) => println!("File contents: {}", contents),
        Err(e) => println!("Error reading file: {}", e),
    }
}

Error Handling

Panic

Panic is a way to handle unrecoverable errors in Rust. When a panic occurs, the program will terminate.

panic!("Something went wrong!");

Recoverable Errors

Recoverable errors can be handled using the Result type.

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

The ? Operator

The ? operator is used to propagate errors in functions that return a Result type.

fn read_username_from_file() -> Result<String, std::io::Error> {
    let mut file = std::fs::File::open("username.txt")?;
    let mut username = String::new();
    file.read_to_string(&mut username)?;
    Ok(username)
}

Scopes

Scopes in Rust are defined by curly braces {}. Variables are valid only within the scope they are declared.

let x = 10;
{
    let x = 5;
    println!("x is: {}", x); // x is valid here
}
println!("x is: {}", x); // will print 10

Closures

Closures are anonymous functions that can capture variables from their surrounding scope.

let add = |a: i32, b: i32| -> i32 {
    a + b
};
let result = add(5, 3); // result is 8

Structs

Structs are custom data types that let you group related data together.

struct Person {
    name: String,
    age: u32,
}
let person = Person {
    name: String::from("Alice"),
    age: 30,
};

Impl

Implementation blocks (impl) are used to define methods for structs or implement method traits.

impl Person {
    fn new(name: String, age: u32) -> Self {
        Self { name, age }
    }
    fn greet(&self) {
        println!("Hello, my name is {} and I am {} years old.", self.name, self.age);
    }
}

Multiple impl blocks can be defined for a single struct.

Traits

Traits are a way to define shared behavior in Rust. They are similar to interfaces in other languages.

trait Greet {
    fn greet(&self);
}

impl Greet for Person {
    fn greet(&self) {
        println!("Hello, my name is {}.", self.name);
    }
}

Derived Traits

Rust provides several built-in traits that can be automatically implemented for your structs using the #[derive] attribute.

#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

Generics

Generics allow you to write flexible and reusable code by defining functions, structs, or enums that can work with different types.

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

Turbo Fish

The turbo fish syntax ::<Type> is used to explicitly specify generic type parameters when calling functions or methods.

let numbers = vec![1, 2, 3, 4, 5];
let largest = largest::<i32>(&numbers);

Lifetimes

Lifetimes are a way to specify how long references are valid in Rust. They help prevent dangling references and ensure memory safety.

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

Modules

Modules are a way to organize code into separate namespaces.

There are 3 ways to declare modules in Rust:

  1. Inline modules
  2. File modules
  3. Directory modules

Inline modules

mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
}
let sum = math::add(5, 3);

File modules

Create a file named math.rs:

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

In your main file:

mod math;
let sum = math::add(5, 3);

Directory modules

Create a directory named math with a file mod.rs:

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

In your main file:

mod math;
let sum = math::add(5, 3);

Use keyword

use keyword is used to bring modules, structs, functions, or traits into scope.

use math::add;
let sum = add(5, 3);

Lib

A Rust project can be of 2 types: binary or library.

A library crate is a collection of code that can be shared and reused across multiple projects. It does not have a main function and cannot be executed directly.

A binary crate is an executable program that has a main function as the entry point.

Each Rust project can be either a library or a binary, or both.

Create a lib.rs file into your binary project can help you to organize your code better.

Create multiple binaries

To create multiple binaries in a single Rust project, you can create a src/bin directory and add separate .rs files for each binary.

Each file in the src/bin directory will be compiled as a separate binary.

Declare a main function in each file.

Tests

Tests in Rust are written in special test modules that are annotated with the #[cfg(test)] attribute.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}

Integration tests

Integration tests are placed in the tests directory at the root of your project.

// tests/integration_test.rs
use my_crate::add;

#[test]
fn test_add() {
    assert_eq!(add(2, 3), 5);
}

Dependencies - crates.io

Crates.io is the Rust community's crate registry. It is a place where you can find and publish Rust libraries and applications.

The official website for crates.io is https://crates.io/.

Go for a backend !

Let's create a simple backend using the Axum framework and PostgreSQL database.

About

Repository used to prepare Rust XpeLab

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors