Rust Cheatsheet: Basic Level

Deepak Ranolia
10 min readDec 3, 2023

--

1 Hello, World!: Lets start with a basic example used in every programming language

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

2 Variables and Mutability: In Rust, variables are immutable by default, meaning that their values cannot be changed once they are assigned. However, you can explicitly declare variables as mutable if you need to modify their values.

let x = 5;
let mut y = 10;
y = y + 1;

3 Data Types: Rust has a rich set of built-in data types that are used to represent and manipulate different kinds of values.

fn main() {
// Scalar Types
let integer: i32 = 42;
let float: f64 = 3.14;
let is_true: bool = true;
let character: char = 'A';

println!("Integer: {}", integer);
println!("Float: {}", float);
println!("Boolean: {}", is_true);
println!("Character: {}", character);

// Compound Types
let tuple: (i32, f64, char) = (42, 3.14, 'A');
let array: [i32; 5] = [1, 2, 3, 4, 5];

println!("Tuple: {:?}", tuple);
println!("Array: {:?}", array);

// Reference Types
let x = 42;
let reference: &i32 = &x;

let array = [1, 2, 3, 4, 5];
let slice: &[i32] = &array[1..4];

println!("Reference: {}", reference);
println!("Slice: {:?}", slice);

// Ownership Types
let string: String = String::from("Hello, Rust!");

let s1 = String::from("hello");
let s2 = s1.clone(); // Cloning creates a new owner
println!("Original String: {}", s1);
println!("Cloned String: {}", s2);

// Special Types
let unit: () = ();

println!("Unit Type: {:?}", unit);
}

4 Functions:

// Function with no parameters and no return value
fn greet() {
println!("Hello, world!");
}

// Function with parameters and return value
fn add_numbers(a: i32, b: i32) -> i32 {
// Returns the sum of two numbers
a + b
}

// Function with a named return value
fn multiply_numbers(a: i32, b: i32) -> i32 {
// Returns the product of two numbers
let result = a * b;
result // No semicolon means it's an implicit return
}

// Function with a default parameter value
fn greet_person(name: &str, greeting: &str) {
// Greets a person with a customizable greeting
println!("{} {}", greeting, name);
}

// Function with a variable number of arguments
fn sum_all_numbers(numbers: &[i32]) -> i32 {
// Sums up all the numbers in the given slice
let mut sum = 0;
for &num in numbers {
sum += num;
}
sum
}

fn main() {
// Calling the greet function
greet();

// Calling functions with parameters and return values
let sum_result = add_numbers(5, 7);
let product_result = multiply_numbers(3, 4);

println!("Sum Result: {}", sum_result);
println!("Product Result: {}", product_result);

// Calling a function with default parameter value
greet_person("Alice", "Hola");

// Calling a function with a variable number of arguments
let numbers = [1, 2, 3, 4, 5];
let total_sum = sum_all_numbers(&numbers);
println!("Total Sum: {}", total_sum);
}

5 Structs:

// Define a simple struct named `Person`
struct Person {
// Struct fields
name: String,
age: u32,
is_student: bool,
}

// Function to create a new Person instance
fn create_person(name: &str, age: u32, is_student: bool) -> Person {
// Returns a new Person instance
Person {
name: String::from(name),
age,
is_student,
}
}

// Function to display information about a Person
fn display_person_info(person: &Person) {
// Displays information about the person
println!("Name: {}", person.name);
println!("Age: {}", person.age);
println!("Is Student: {}", person.is_student);
}

fn main() {
// Creating instances of the Person struct
let person1 = create_person("Alice", 25, true);
let person2 = create_person("Bob", 30, false);

// Displaying information about the persons
display_person_info(&person1);
display_person_info(&person2);
}

6 Enums:

// Define an enum named `Day` representing days of the week
enum Day {
// Enum variants with no associated values
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
// Enum variant with an associated value
Saturday(u32), // Represents the day with a date
Sunday,
}

// Function to process a Day enum and return a string
fn process_day(day: Day) -> String {
// Match expression to handle each enum variant
match day {
Day::Monday => String::from("Start of the workweek!"),
Day::Friday => String::from("Weekend is almost here!"),
Day::Saturday(date) => format!("Weekend! Date: {}", date),
Day::Sunday => String::from("Chill day!"),
_ => String::from("A regular day."),
}
}

fn main() {
// Using enum variants
let monday = Day::Monday;
let saturday = Day::Saturday(24);

// Processing enum variants and displaying results
println!("Monday: {}", process_day(monday));
println!("Saturday: {}", process_day(saturday));
}

7 Pattern Matching: is often done using the match expression, which allows you to compare a value against a set of patterns and execute code based on the matching pattern.

// Define an enum named `Shape` with variants
enum Shape {
Circle(f64), // Circle variant with a radius
Rectangle(f64, f64), // Rectangle variant with width and height
Square(f64), // Square variant with a side length
}

// Function to calculate area based on different shapes
fn calculate_area(shape: &Shape) -> f64 {
match shape {
// Match Circle variant and extract the radius
Shape::Circle(radius) => 3.14159 * radius * radius,
// Match Rectangle variant and extract width and height
Shape::Rectangle(width, height) => width * height,
// Match Square variant and extract the side length
Shape::Square(side) => side * side,
}
}

fn main() {
// Using the Shape enum to represent different shapes
let circle = Shape::Circle(5.0);
let rectangle = Shape::Rectangle(4.0, 6.0);
let square = Shape::Square(3.0);

// Calculating and displaying the area of each shape
println!("Circle Area: {:.2}", calculate_area(&circle));
println!("Rectangle Area: {:.2}", calculate_area(&rectangle));
println!("Square Area: {:.2}", calculate_area(&square));
}

8 Vectors:

fn main() {
// Create an empty vector of integers
let mut empty_vector: Vec<i32> = Vec::new();

// Add elements to the vector
empty_vector.push(1);
empty_vector.push(2);
empty_vector.push(3);

// Create a vector with initial values
let initial_vector = vec![4, 5, 6];

// Combine vectors using the `extend` method
empty_vector.extend(&initial_vector);

// Access elements in the vector by index
let second_element = empty_vector[1];

// Iterate over the vector using a for loop
for element in &empty_vector {
println!("Element: {}", element);
}

// Iterate over the vector and modify values using the `iter_mut` method
for element in &mut empty_vector {
*element += 1; // Increment each element by 1
}

// Using the `len` method to get the length of the vector
let vector_length = empty_vector.len();

// Using the `pop` method to remove the last element from the vector
let popped_element = empty_vector.pop();

// Check if the vector contains a specific value using the `contains` method
let contains_value = empty_vector.contains(&5);

// Print vector information
println!("Vector: {:?}", empty_vector);
println!("Second Element: {}", second_element);
println!("Vector Length: {}", vector_length);
println!("Popped Element: {:?}", popped_element);
println!("Contains 5: {}", contains_value);
}

9 Loops:

fn main() {
// While Loop: Execute the block as long as the condition is true
let mut counter = 0;
while counter < 5 {
println!("While Loop: Counter = {}", counter);
counter += 1;
}

// For Loop: Iterate over a range of values
for i in 0..5 {
println!("For Loop: i = {}", i);
}

// For Loop with Iterator: Iterate over elements in a collection
let fruits = vec!["Apple", "Banana", "Orange"];
for fruit in fruits.iter() {
println!("For Loop with Iterator: {}", fruit);
}

// Loop: Infinite loop that can be exited using a break statement
let mut infinite_counter = 0;
loop {
println!("Infinite Loop: Counter = {}", infinite_counter);
if infinite_counter >= 3 {
break; // Exit the loop when the counter is greater than or equal to 3
}
infinite_counter += 1;
}

// Nested Loops: Using nested loops
for i in 1..=3 {
for j in 1..=2 {
println!("Nested Loop: i = {}, j = {}", i, j);
}
}
}

10 Ownership and Borrowing: are key concepts in Rust that manage memory safety without the need for a garbage collector.

fn main() {
// Ownership Example

// Create a String with ownership
let owner_string = String::from("Hello, Ownership!");

// Pass ownership to a function and print the string
print_owned_string(owner_string);

// Error: The following line won't compile because ownership was transferred
// println!("Trying to use the string: {}", owner_string);

// Borrowing Example

// Create a String
let original_string = String::from("Hello, Borrowing!");

// Borrow the string using a reference
print_borrowed_string(&original_string);

// The original string can still be used after borrowing
println!("Original string is still usable: {}", original_string);
}

// Function taking ownership of a String and printing it
fn print_owned_string(s: String) {
println!("Owned String: {}", s);
// Ownership is transferred to the function, and the String will be dropped when the function ends
}

// Function borrowing a String using a reference and printing it
fn print_borrowed_string(s: &String) {
println!("Borrowed String: {}", s);
// The function borrows the String, and ownership remains with the original owner
}

11 Option and Result: are two enums in Rust that represent the presence or absence of a value (Option) or the success or failure of an operation (Result).

// Option Cheatsheet

fn get_value_or_default(option_value: Option<i32>) -> i32 {
// Unwrapping the Option using `unwrap_or` to provide a default value
option_value.unwrap_or(42)
}

fn main() {
// Creating Some and None variants of Option
let some_value: Option<i32> = Some(10);
let none_value: Option<i32> = None;

// Unwrapping the Some variant to get the value
let unwrapped_value = some_value.unwrap();

// Using match to handle both Some and None variants
match none_value {
Some(value) => println!("Some Variant: {}", value),
None => println!("None Variant"),
}

// Using the `get_value_or_default` function
let default_value = get_value_or_default(None);

// Print Option information
println!("Unwrapped Value: {}", unwrapped_value);
println!("Default Value: {}", default_value);
}


// Result Cheatsheet

use std::fs::File;
use std::io::Error;

fn open_file(file_path: &str) -> Result<File, Error> {
// Attempting to open a file and returning a Result
File::open(file_path)
}

fn main() {
// Creating Ok and Err variants of Result
let successful_result: Result<i32, &str> = Ok(42);
let error_result: Result<i32, &str> = Err("File not found");

// Unwrapping the Ok variant to get the value
let unwrapped_value = successful_result.unwrap();

// Using match to handle both Ok and Err variants
match error_result {
Ok(value) => println!("Ok Variant: {}", value),
Err(error) => println!("Err Variant: {}", error),
}

// Using the `open_file` function
match open_file("example.txt") {
Ok(file) => println!("File opened successfully: {:?}", file),
Err(err) => println!("Error opening file: {}", err),
}
}

12 Error Handling with Result:

// Function that returns a Result
fn divide(a: f64, b: f64) -> Result<f64, &'static str> {
if b == 0.0 {
// Returning an Err variant if division by zero is attempted
Err("Cannot divide by zero")
} else {
// Returning an Ok variant with the result of the division
Ok(a / b)
}
}

fn main() {
// Using the divide function and handling the Result

// Case 1: Successful division
match divide(10.0, 2.0) {
Ok(result) => println!("Result of division: {}", result),
Err(error) => println!("Error: {}", error),
}

// Case 2: Attempting to divide by zero
match divide(8.0, 0.0) {
Ok(result) => println!("Result of division: {}", result),
Err(error) => println!("Error: {}", error),
}

// Chaining Result operations using map and and_then

// Case 3: Chaining operations with map
let result_squared = divide(9.0, 3.0)
.map(|result| result * result)
.map_err(|error| format!("Squared Error: {}", error));

// Case 4: Chaining operations with and_then
let result_cubed = divide(27.0, 3.0)
.and_then(|result| divide(result, 3.0))
.map_err(|error| format!("Cubed Error: {}", error));

// Printing the results of chained operations
match result_squared {
Ok(result) => println!("Squared Result: {}", result),
Err(error) => println!("Squared Error: {}", error),
}

match result_cubed {
Ok(result) => println!("Cubed Result: {}", result),
Err(error) => println!("Cubed Error: {}", error),
}
}

13 Modules and Packages: are a way to organize code within a file, and packages are a way to organize code across multiple files.

// Define a module named 'math' in the same file
mod math {
// Define a function within the 'math' module
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}

// Import the 'math' module into the current scope
use math::add;

// Define a module named 'geometry' in a separate file 'geometry.rs'
mod geometry;

// Use the 'geometry' module's functions
use geometry::{calculate_area, calculate_volume};

fn main() {
// Use the 'add' function from the 'math' module
let sum = add(3, 4);
println!("Sum: {}", sum);

// Use functions from the 'geometry' module
let area = calculate_area(5.0, 3.0);
let volume = calculate_volume(2.0, 4.0, 6.0);

// Print the results
println!("Area: {}", area);
println!("Volume: {}", volume);
}

14 Lifetime Annotations: are used to explicitly specify the lifetimes of references in functions and structs.

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

// Struct with lifetime annotations
struct Person<'a> {
name: &'a str,
age: u32,
}

impl<'a> Person<'a> {
// Method with lifetime annotations
fn get_name(&self) -> &'a str {
self.name
}
}

fn main() {
let string1 = String::from("hello");
let result;
{
let string2 = String::from("world");

// Using the 'longest' function with explicit lifetimes
result = longest(string1.as_str(), string2.as_str());
println!("The longest string is: {}", result);
}
// The 'string2' variable is out of scope, but 'result' still refers to valid data

// Creating a Person instance with explicit lifetime for the name reference
let person = Person { name: "Alice", age: 30 };

// Using the 'get_name' method with explicit lifetime
println!("Person's name: {}", person.get_name());
}

15 Traits: are a way to define shared behavior between types.

// Define a trait named 'Shape'
trait Shape {
// Declare a method without a default implementation
fn area(&self) -> f64;

// Declare a method with a default implementation
fn display(&self) {
println!("This is a shape.");
}
}

// Implement the 'Shape' trait for a struct 'Circle'
struct Circle {
radius: f64,
}

impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}

// Overriding the default implementation of the 'display' method
fn display(&self) {
println!("This is a circle with radius {}.", self.radius);
}
}

// Implement the 'Shape' trait for a struct 'Rectangle'
struct Rectangle {
width: f64,
height: f64,
}

impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}

fn print_area(shape: &dyn Shape) {
// Polymorphic function that can accept any type implementing the 'Shape' trait
println!("Area: {}", shape.area());
}

fn main() {
// Create instances of 'Circle' and 'Rectangle'
let circle = Circle { radius: 5.0 };
let rectangle = Rectangle { width: 4.0, height: 6.0 };

// Call the 'display' method on instances of 'Circle' and 'Rectangle'
circle.display();
rectangle.display();

// Call the 'print_area' function with instances of 'Circle' and 'Rectangle'
print_area(&circle);
print_area(&rectangle);
}

Subscribe to read upcoming levels on the go. Thank you

--

--

Deepak Ranolia
Deepak Ranolia

Written by Deepak Ranolia

Strong technical skills, such as Coding, Software Engineering, Product Management & Finance. Talk about finance, technology & life https://rb.gy/9tod91

No responses yet