Offensive Rust For Hacker’s (Part — 3) | Enums, Generic, Traits, Module & Crates

D3athCod3
7 min readJul 16, 2024

--

In this part, we’ll cover some of the more advanced features of Rust, including enums, generics, traits, and modules. By the end of this blog, you’ll have a solid understanding of these topics and be ready to use them in your programs and tools. So without wasting any more time, let’s dive in!

Enums

Enums, short for “enumerations,” are a powerful feature in Rust that allow you to define a type by enumerating its possible variants. They are useful for representing a value that could be one of several different types. Enums can have variants with or without associated data, making them versatile for various use cases.

For Example —

enum TrafficLight {
Red,
Yellow,
Green,
}

fn main() {
let light = TrafficLight::Red;

match light {
TrafficLight::Red => println!("Stop!"),
TrafficLight::Yellow => println!("Slow down."),
TrafficLight::Green => println!("Go!"),
}
}

Code Explanation —

  • We define an enum TrafficLight with three variants: Red, Yellow, and Green.
  • We create a variable light and set it to TrafficLight::Red.
  • We use a match statement to determine the action based on the current state of the light.
  • The program prints "Stop!" since light is TrafficLight::Red.

Enums with Associated Values

In Rust, enum variants can be initialized with associated values, allowing for flexible and expressive data representation.

For Example —

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

fn main() {
let msg1 = Message::Move { x: 10, y: 20 };
let msg2 = Message::Write(String::from("Hello, Rust!"));

match msg1 {
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
_ => (),
}

match msg2 {
Message::Write(text) => println!("Message: {}", text),
_ => (),
}
}

Code Explanation —

  • The Message enum now has two variants: Move with x and y coordinates, and Write with a String message.
  • We create instances msg1 and msg2 with different variants.
  • We use match statements to destructure and access the data inside the enum variants.

Generic

Generics in Rust allow you to write code that can handle multiple types without sacrificing type safety or performance. They enable you to abstract over different types, reducing code duplication and increasing code reusability.

For Example —

// Define a generic function that returns the larger of two values
fn larger<T: PartialOrd>(a: T, b: T) -> T {
if a >= b {
a
} else {
b
}
}

fn main() {
// Call the generic function with integers
let result1 = larger(5, 10);
println!("Larger number: {}", result1); // Output: Larger number: 10

// Call the generic function with floats
let result2 = larger(3.5, 1.2);
println!("Larger number: {}", result2); // Output: Larger number: 3.5

// Call the generic function with strings
let result3 = larger("apple", "banana");
println!("Larger string: {}", result3); // Output: Larger string: banana
}

Code Explanation —

  • larger is a generic function that takes two parameters of the same type T, which must implement the PartialOrd trait (for comparison).
  • We call larger with integers (5 and 10), floats (3.5 and 1.2), and strings ("apple" and "banana").
  • Rust’s type inference system deduces the specific types (i32, f64, and &str in this case) when calling generic functions.

Traits

Traits in Rust provide a way to define shared behavior across types. They are similar to interfaces in other languages and allow you to define methods that types can implement. Traits are essential for achieving code reuse and enabling polymorphism in Rust programs.

For Example —

// Define a trait named `Drawable`
trait Drawable {
fn draw(&self);
}

// Implement the `Drawable` trait for a `Circle` struct
struct Circle {
radius: f64,
}

impl Drawable for Circle {
fn draw(&self) {
println!("Drawing a circle with radius {}", self.radius);
}
}

// Function to draw any object that implements `Drawable`
fn draw_shape<T: Drawable>(shape: T) {
shape.draw();
}

fn main() {
let circle = Circle { radius: 5.0 };

draw_shape(circle);
}

Code Explanation —

  • Drawable trait defines a method draw(&self) that prints how an object should be drawn.
  • Circle struct implements the Drawable trait by providing a specific implementation for draw.
  • draw_shape function accepts any type T that implements Drawable and calls its draw method.
  • An instance of Circle is passed to draw_shape, demonstrating the trait implementation in action.

Modules & Crates

Components of a Rust Project

Before learning about modules and crates let’s first understand what things we get after creating a new rust project

  1. src/ Directory —

This directory contains your project's source code files. It's where you write your Rust code.

main.rs: The main entry point of your Rust program. This file usually contains the main function, which is the starting point of execution for your program.

Other .rs Files: These are additional Rust source files that you create to organize your code into modules or to separate concerns. You can name these files according to their purpose, such as utils.rs, calc.rs, etc.

2. Cargo.toml —

Cargo.toml is the configuration file for Rust projects. It resides at the root of your project directory and contains metadata about your project and its dependencies.

[package] Section: Specifies metadata about the package, including its project name, version, authors, and edition of Rust to use (2021 in this case).

[dependencies] Section: Lists the dependencies (external crates) that your project relies on. Dependencies can be added here to include external libraries. More on this later :)

3. Cargo.lock

Ensures reproducible builds by recording exact versions of dependencies used in the project, preventing automatic updates that could introduce compatibility issues.

4. target/ Directory —

Contains compiled binaries, libraries, and temporary build artifacts generated by Cargo during the build process, keeping them separate from source code for cleanliness and efficiency.

Organizing Code with Modules

Let’s understand how we can organize the logic of a calculator program into separate modules and files.

In below example we are defining our program logic in different file named as src/calc.rs

// Define a module named `calc`
pub mod calc {
// Function to add two numbers
pub fn add(a: f64, b: f64) -> f64 {
a + b
}

// Function to subtract two numbers
pub fn subtract(a: f64, b: f64) -> f64 {
a - b
}

// Function to multiply two numbers
pub fn multiply(a: f64, b: f64) -> f64 {
a * b
}

// Function to divide two numbers
pub fn divide(a: f64, b: f64) -> f64 {
if b != 0.0 {
a / b
} else {
panic!("Division by zero error!");
}
}
}

In src/main.rs

// Bring the `calc` module into scope
mod calc;

fn main() {
let x = 10.0;
let y = 5.0;

// Using functions from the `calc` module
let sum = calc::add(x, y);
let difference = calc::subtract(x, y);
let product = calc::multiply(x, y);
let quotient = calc::divide(x, y);

// Printing results
println!("{} + {} = {}", x, y, sum);
println!("{} - {} = {}", x, y, difference);
println!("{} * {} = {}", x, y, product);
println!("{} / {} = {}", x, y, quotient);
}

Code Explanation —

  • src/calc.rs:

Defines a module named calc that encapsulates the logic for basic arithmetic operations.

Each function (add, subtract, multiply, divide) is marked with pub to make them accessible from outside the module.

  • src/main.rs:

Imports the calc module (mod calc;), making its contents available.

Uses functions from the calc module (calc::add, calc::subtract, calc::multiply, calc::divide) to perform calculations and print results.

Crates

In Rust, a “crate” refers to a package or a module or library of Rust code that can be compiled and shared. You can found lot of libraries for different use case online anyone can use them in their program. It can contain one or more modules, which are collections of Rust code files that work together.

For Example —

  • Using a Crate (chrono):
// Importing the `chrono` crate to work with dates and times
use chrono::{DateTime, Local};

fn main() {
// Getting the current date and time
let now: DateTime<Local> = Local::now();

// Printing the current date and time
println!("Current date and time: {}", now);
}

you can read library documentation with a simple google search :)

Like for chrono you can use: https://crates.io/crates/chrono

Note:

For using these crates you had to mention crate dependencies inside cargo.toml file else you going to get bunch of errors :)

For above example —

To create offensive tools, we’ll use many crates to make development easier and more efficient.

Questions For You —

  • Create an enum Shape that represents different geometric shapes (Circle, Square, Triangle). Implement a function area that calculates the area of each shape using pattern matching.
  • Write a generic function largest that takes a list of integers or floats and returns the largest value. Test the function with different types of lists.
  • Define a trait Drawable with a method draw that prints "Drawing a {shape}" where {shape} is replaced by the name of the implementing struct. Implement this trait for a struct Rectangle.
  • Create a program for finding prime number and hide logic from main function.

Conclusion

In this part of “Offensive Rust For Hackers,” we explored crucial concepts like crates and modules, integral to almost every Rust program. Building on these foundations, the next segment will focus on practical tool development, applying our knowledge to create effective solutions using both new and previously learned techniques.

Stay Tuned :) & Happy Coding Hacker’s !!

If you’d like to support us, consider using the “Buy me a Coffee” button below!

--

--

No responses yet