🦀 - Rust

Rust #


Introduction #


Rust is a high-level, multi-paradigm programming language that prioritizes performance, safety, and concurrency. It employs a unique ownership model with zero-cost abstractions, move semantics, and guaranteed memory safety to eliminate common programming errors like null pointer dereferencing and data races at compile-time. Rust’s static typing and type inference provide a robust type system, while its trait-based generics and pattern matching offer expressive high-level abstractions.

Rust’s performance is comparable to that of C and C++, but it provides better memory safety. It doesn’t use a garbage collector, making it suitable for performance-critical applications and the use in embedded systems. The language also supports a variety of programming paradigms, including procedural, concurrent actor, object-oriented, and pure functional styles.

Rust’s ecosystem includes Cargo, a build system and package manager that simplifies dependency management. The Rust compiler’s clear error messages and built-in test and benchmarking tools contribute to developer productivity. Rust also supports WebAssembly as a first-class citizen, enabling high-performance web applications.

The Rust community actively contributes to a growing collection of libraries, known as “crates”, which extend the language’s capabilities. The language’s adoption in significant projects like the Servo web engine, the Redox operating system, and the Tor project attest to its capabilities. Notably, Rust was chosen for the development of the Linux kernel, marking a significant milestone in its history.

In summary, Rust’s design balances performance, robustness, and expressiveness, making it a powerful tool for a wide range of software development tasks. Its innovative features and growing ecosystem position it as a compelling choice for modern system programming.

Articles #


#[rustfmt::skip]
An attribute you can place above code to tell rustfmt not to format it.
Build, Run, and Check Your Rust Projects
This document explains how to build, run and check your Rust projects with cargo.
Change Default Compiler to Nightly
How to change to the nightly rust compiler for one build or setting it up as the default for your project.
config.toml
Configuring cargo, the Rust package manager.
Error Handling
An in-depth guide on error handling in Rust, covering basic techniques with Result, the ? operator, custom error types, asynchronous error handling, and the use of the anyhow and thiserror crate for more flexible error management.
Limit Number of Cargo Build Jobs
How to limit the jobs cargo spawns.
Metaprogramming with Macros
Monomorphization
Monomorphization in Rust is a compile-time process where the compiler replaces polymorphic functions with specific versions for each unique type instantiation, optimizing for speed at the cost of binary size.
Mutex
In Rust, a Mutex (Mutual Exclusion) is a synchronization primitive that ensures thread safety by allowing only one thread to access shared data at a time, and it provides mechanisms for locking and unlocking, deadlock prevention, and handling of “poisoned” states.
sccache
Sccache is a ccache-like tool developed by Mozilla that acts as a compiler wrapper, avoiding compilation when possible by caching and reusing intermediate build artifacts across multiple builds, supporting various languages including C/C++, Rust, and NVIDIA’s CUDA.
Silence Compiler Warnings
The command cargo rustc -- -Awarnings and the environment variable setting RUSTFLAGS="-Awarnings" in Rust are used to silence all compiler warnings.
Sum and Product Types
An exploration of sum types (enums) and product types (structs) in Rust. Learn how these fundamental concepts can be used to create robust and = game architectures.
unreachable!()
Explains the unreachable!() macro in Rust, which is used to mark code sections that should never be reached, causing the program to panic if executed.
Update All Installed Cargo Packages
Explains how to update every package installed with cargo.
vec Operations
Provides an overview of various operations you can perform on vectors in Rust, including sorting, searching, filtering, mapping, custom comparators, deduplication, finding elements, folding, and managing capacity and length.
VecDeque
Explains how the BufRead trait in Rust was implemented for VecDeque<u8> in Rust 1.75, allowing efficient buffered reading from a double-ended queue with examples demonstrating its usage.
Visibility
Explains how to control the visibility of functions and other items in Rust using the pub keyword, including examples of public, crate-level, and private visibility.
Aho–Corasick Algorithm
The Aho-Corasick algorithm efficiently locates all occurrences of multiple keywords in a text by constructing a finite state machine, combining trie and failure transitions, and can be used for tasks like pattern highlighting and simultaneous replacements.
bytemuck
The bytemuck crate offers Rust developers a safe, zero-cost way to perform type transmutations, crucial for low-level systems programming and interfacing with foreign data structures.
Verbose Cargo Logging
Environment variable used to set the logging level of the Cargo’s compiler fingerprinting to info.
bevy
Bevy is a refreshingly simple, data-driven game engine built in Rust, offering a complete 2D and 3D feature set, and it is free and open-source.
Memory Regions and Management

Installing Rust on Linux #


  1. Open a terminal.
  2. Download and install rustup by running the following command:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  1. The above command will download a script and start the installation. You will be prompted for installation options. You can proceed with the default options by hitting enter.
  2. Close the terminal and reopen it.
  3. Verify the installation by running the following command:
rustc --version

Installing Rust on Windows #


  1. Go to https://www.rust-lang.org/tools/install to download rustup-init.exe.
  2. Run rustup-init.exe and follow the onscreen instructions.
  3. After the installation is complete, close the command prompt and reopen it.
  4. Verify the installation by running the following command:
rustc --version

Creating Your First Rust Program #


Rust has a build system and package manager called cargo. It comes with rustup, so you already have it if you’ve installed Rust.

  1. Open a terminal or command prompt.
  2. Navigate to the directory where you want to create your new Rust project.
  3. Run the following command:
cargo new hello_world

This will create a new directory called hello_world with a basic Rust project.

  1. Navigate into the hello_world directory:
cd hello_world
  1. Open the src/main.rs file in a text editor. You’ll see the following code:
fn main() {
    println!("Hello, World!");
}
  1. To run the program, go back to your terminal and run the following command:
cargo run

You should see Hello, World! printed to the console. That’s your first Rust program!

Cargo.toml #


When you create a new Rust project using cargo new hello_world, a Cargo.toml file is automatically generated in the hello_world directory. This file is used by Cargo, Rust’s package manager and build system, to manage dependencies and build settings for your project.

Here’s what a basic Cargo.toml file looks like:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2018"

[dependencies]

Let’s break down the sections of this file:

  • [package]: This section contains information about the package (your project).

    • name: The name of the package. This is set to the name of your project, hello_world.
    • version: The version of the package. This follows the Semantic Versioning scheme.
    • edition: The edition of Rust to use. This is usually set to the latest stable edition.
  • [dependencies]: This section is where you list your package’s dependencies. Each dependency is listed as a key-value pair, with the key being the dependency’s name and the value being the version requirement.

To add a dependency to your project, you would add a line to the [dependencies] section with the name of the dependency and the version number. For example, if you wanted to add the rand crate (a library for generating random numbers), you would add the following line:

rand = "0.8.3"

So your Cargo.toml file would now look like this:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2018"

[dependencies]
rand = "0.8.3"

When you build your project with cargo build, Cargo will automatically download and compile your dependencies and link them to your project.

The Cargo.toml file can also contain other sections for more advanced use cases, such as [dev-dependencies] for dependencies that are only used during development, and [build-dependencies] for dependencies that are used in build scripts.

Scopes #


In Rust, a scope is a section of your program where a binding is valid. When the scope ends, the binding is not valid anymore. This is also known as variable shadowing. Let’s look at an example:

fn main() {
    let x = 5;

    {
        let x = x + 1;
        println!("Inside the inner scope, x is: {x}");
    }

    println!("Outside the inner scope, x is: {x}");
}

In this example, there are two scopes: the main function scope and the inner scope. The variable x is declared twice, once in each scope.

In the inner scope, x is redeclared and its value is set to the value of x from the outer scope plus one. The println! statement inside the inner scope will output 6, because x in the inner scope is 6.

The println! statement outside the inner scope will output 5, because x in the outer scope is 5. The x in the inner scope does not change the value of the x in the outer scope because they are in different scopes.

Printing Text #


In Rust, there are several ways to print text to the console. Here are a few examples:

Printing text with println! macro #

The println! macro is the most common way to print text in Rust. It prints the text and a newline to the console.

fn main() {
    println!("Hello, World!");
}

Printing formatted text with println! macro #

You can also use the println! macro to print formatted text. The {name} syntax is used to insert variables into the text.

fn main() {
    let name = "Alice";
    println!("Hello, {name}!");
}

Printing with print! macro #

The print! macro is similar to println!, but it does not add a newline at the end.

fn main() {
    print!("Hello, ");
    print!("World!");
}

Printing with format! macro #

The format! macro is similar to println!, but instead of printing the text, it returns a String. This can be useful when you want to format some text but use it later in the code.

fn main() {
    let name = "Alice";
    let greeting = format!("Hello, {name}!");
    println!("{}", greeting);
}

Printing with eprintln! and eprint! macros #

The eprintln! and eprint! macros are similar to println! and print!, but they print to the standard error (stderr) instead of the standard output (stdout).

fn main() {
    eprintln!("This is an error message!");
}

Printing Debug Output with dbg! Macro #

The dbg! macro is used for quick and dirty debugging. It prints the filename and line number of its location, as well as the expression and its value.

fn main() {
    let name = "Alice";
    dbg!(name);
}

Printing with write! and writeln! Macros #

The write! and writeln! macros are similar to print! and println!, but they write to a Write trait object, such as a file or a network stream. Here’s an example of writing to a String:

use std::fmt::Write;

fn main() {
    let mut s = String::new();
    writeln!(&mut s, "Hello, {}!", "World").expect("Unable to write");
    println!("{s}");
}

Printing Custom Types with fmt::Display and fmt::Debug #

If you have a custom type and you want to be able to print it using println!, you can implement the fmt::Display or fmt::Debug trait for that type. Here’s an example:

use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let p = Point { x: 10, y: 20 };
    println!("{p}");
}

In this example, we define a Point struct and implement the fmt::Display trait for it. This allows us to print a Point using println!.

Printing with format_args! Macro #

The format_args! macro is a low-level macro for string formatting, which println!, print!, format!, write!, and writeln! are built upon. It takes a format string and a list of arguments to substitute into the format string:

fn main() {
    let s = format_args!("{} {}", "Hello", "World");
    println!("{s}");
}

Printing with panic! and assert! Macros #

While not typically used for printing text in the same way as println! or print!, the panic! and assert! macros do print text to the console when a certain condition isn’t met, and then exit the program:

fn main() {
    panic!("This is a panic message!");

    let x = 5;
    assert!(x == 10, "x is not equal to 10!");
}

Printing with log Crate #

For more complex applications, you might want to use the log crate along with a logging backend crate like env_logger. This allows you to print text with different levels of severity (error, warning, info, debug, trace) and control the verbosity of the output:

use log::{info, trace, warn};

fn main() {
    env_logger::init();

    info!("This is an info message");
    warn!("This is a warning message");
    trace!("This is a trace message");
}

Remember to add log and env_logger to your Cargo.toml:

[dependencies]
log = "0.4"
env_logger = "0.11.3"

The logging level for the log crate in Rust can be set using the RUST_LOG environment variable when you run your program. This variable controls the output of the log crate and can be set to different levels of verbosity.

Here’s how you can set it:

  1. In your terminal, before running your program:

Bash:

export RUST_LOG=info
cargo run

PowerShell:

$env:RUST_LOG="info"
cargo run

In this example, info is the logging level. You can replace it with error, warn, debug, or trace depending on your needs.

  1. Directly in the command line when running your program:

Bash:

RUST_LOG=info cargo run

PowerShell:

$env:RUST_LOG="info"; cargo run
  1. In your Rust code, using the env_logger crate:
use log::LevelFilter;
use env_logger::Builder;

fn main() {
    Builder::new()
        .filter(None, LevelFilter::Info)
        .init();

    // The rest of your amazing app..
}

In this example, LevelFilter::Info sets the logging level to info. You can replace it with LevelFilter::Error, LevelFilter::Warn, LevelFilter::Debug, or LevelFilter::Trace depending on your needs.

⚠️ Reminder!

The log crate itself does not implement any logging functionality. It is a facade that abstracts over the actual logging implementation. env_logger is one such implementation that reads its configuration from environment variables.