Std Types
Box
All values in Rust are stack allocated by default. Values can be boxed (allocated on the heap) by creating a Box<T>
. A box is a smart pointer to a heap allocated value of type T
.
Boxed values can be dereferenced using the *
operator.
// Define a struct to represent a person
struct Person {
name: String,
age: u32,
}
// A function that creates a Boxed Person
fn create_person(name: &str, age: u32) -> Box<Person> {
let person = Person {
name: String::from(name),
age,
};
// Box the person and return the Box
Box::new(person)
}
fn main() {
// Create a boxed person
let boxed_person = create_person("Alice", 30);
// Access the fields through dereferencing
println!("Name: {}, Age: {}", boxed_person.name, (*boxed_person).age);
// Name: Alice, Age: 30
// The memory for the person is automatically deallocated when the box goes out of scope
}
Vectors
Vectors are resizable arrays. Like slices, their size is not known at compile time, but they can grow or shrink at any time.
fn main() {
// Creating a vector with initial values
let mut numbers: Vec<i32> = vec![1, 2, 3, 4, 5];
// Accessing elements
println!("Original Vector: {:?}", numbers);
// Adding an element to the vector
numbers.push(6);
println!("After pushing 6: {:?}", numbers);
// Removing the last element
let popped = numbers.pop();
println!("Popped element: {:?}", popped);
println!("After popping: {:?}", numbers);
// Iterating over the vector
println!("Doubling each element:");
for num in &mut numbers {
*num *= 2;
println!("{}", num);
}
// Creating a new vector and concatenating it with the original vector
let more_numbers = vec![7, 8, 9];
numbers.extend(more_numbers);
println!("After extending with more_numbers: {:?}", numbers);
// Using the `contains` method
let contains_8 = numbers.contains(&8);
println!("Does the vector contain 8? {}", contains_8);
// Creating a vector of squares using the `map` method
let squares: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
println!("Vector of squares: {:?}", squares);
}
Strings
There are two types of strings in Rust: String
and &str
.
A String
is stored as a vector of bytes (Vec<u8>
), which is heap allocated, growable and mutable.
fn main() {
// Creating a new empty String
let mut my_string = String::new();
// Appending a literal string
my_string.push_str("Hello, ");
// Appending another String
let name = "Alice";
my_string.push_str(name);
// Displaying the result
println!("String: {}", my_string);
// Converting a String to a &str for borrowing
let borrowed_str: &str = &my_string;
println!("Borrowed &str: {}", borrowed_str);
}
&str
is a slice (&[u8]
) that always points to a valid UTF-8 sequence, which can reference a portion of a String
, a string literal, or another &str
.
fn main() {
// Creating a string literal
let hello_str: &str = "Hello, ";
// Creating another string literal
let name_str: &str = "Bob";
// Concatenating string literals to form a new &str
let greeting: &str = hello_str.to_owned() + name_str;
// Displaying the result
println!("Concatenated &str: {}", greeting);
}
String Escapes
There are multiple ways to write string literals with special characters in them. Generally special characters are escaped with a backslash character: \
.
fn main() {
let orig_str = "Escapes work here: \x3F \u{211D}";
println!("{}", orig_str);
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
println!("{}", raw_str);
// If you need quotes in a raw string, add a pair of #s
let quotes = r#"He said: "It's impossible!""#;
println!("{}", quotes);
// If you need "# in your string, just use more #s in the delimiter.
let longer_delimiter = r###"It's a hashtag "#rust in it. And even "##!"###;
println!("{}", longer_delimiter);
}
Option
The Option<T>
enum has two variants:
Some(value)
: a tuple struct that has avalue
with typeT
None
: lack of value
fn find_even_number(numbers: &[u32]) -> Option<u32> {
for &num in numbers {
if num % 2 == 0 {
return Some(num);
}
}
None
}
fn main() {
let numbers = vec![1, 3, 5, 2, 8, 9];
match find_even_number(&numbers) {
Some(even_num) => println!("Found even number: {}", even_num),
None => println!("No even number found."),
}
}
Result
The Result<T, E>
enum has two variants:
Ok(value)
: it has avalue
with typeT
Err(reason)
: it explains the cause of the failure with the reason, which has typeE
// Function that returns a Result<u32, String> based on a condition
fn divide(a: u32, b: u32) -> Result<u32, String> {
if b == 0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(a / b)
}
}
fn main() {
let result2 = divide(8, 0);
match result2 {
Ok(quotient) => println!("Result: Quotient is {}", quotient),
Err(error) => println!("Result: Error - {}", error),
}
}
The ?
operator is a shorthand for propagating errors in Rust. It can be used in functions that return a Result
, allowing for more concise error handling.
use std::fs::File;
use std::io::{self, Read};
fn read_file_contents(file_path: &str) -> Result<String, io::Error> {
// Attempt to open the file
let mut file = File::open(file_path)?;
// Read the contents of the file into a String
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
panic!
In Rust, the panic!
macro is used to cause the current thread to panic, which results in unwinding the stack and potentially terminating the program. Panicking is typically reserved for unrecoverable errors or situations where continuing execution would lead to undefined behavior.
fn main() {
let divisor = 0;
if divisor == 0 {
// Panicking if attempting to divide by zero
panic!("Attempted to divide by zero!");
}
// This code will not be reached if panic occurs
let result = 42 / divisor;
println!("Result: {}", result);
}
HashMap
In Rust, you can use the HashMap
data structure from the std::collections
module to store key-value pairs.
use std::collections::HashMap;
fn main() {
// Creating an empty HashMap
let mut my_map = HashMap::new();
// Inserting key-value pairs into the HashMap
my_map.insert("one", 1);
my_map.insert("two", 2);
my_map.insert("three", 3);
my_map.insert("literank", 58);
// Accessing values by key
match my_map.get("two") {
Some(&value) => println!("The value of 'two' is: {}", value),
None => println!("Key 'two' not found."),
}
// Updating a value
my_map.insert("three", 30);
// Iterating over the HashMap
println!("Iterating over the HashMap:");
for (key, value) in &my_map {
println!("Key: {}, Value: {}", key, value);
}
// Removing a key-value pair
my_map.remove("literank");
// Checking if a key exists
if my_map.contains_key("literank") {
println!("Key 'literank' is present.");
} else {
println!("Key 'literank' is not present.");
}
}
HashSet
The HashSet
data structure, provided by the std::collections
module, allows you to store a collection of unique values.
use std::collections::HashSet;
fn main() {
// Creating an empty HashSet
let mut my_set = HashSet::new();
// Inserting elements into the HashSet
my_set.insert("apple");
my_set.insert("banana");
my_set.insert("orange");
// Checking if an element is in the HashSet
if my_set.contains("banana") {
println!("The set contains 'banana'.");
} else {
println!("The set does not contain 'banana'.");
}
// Removing an element from the HashSet
my_set.remove("apple");
// Iterating over the HashSet
println!("Iterating over the HashSet:");
for fruit in &my_set {
println!("Fruit: {}", fruit);
}
// Clearing the HashSet
my_set.clear();
// Checking if the HashSet is empty
if my_set.is_empty() {
println!("The HashSet is empty.");
} else {
println!("The HashSet is not empty.");
}
}
Rc (Reference Counting)
Rc
(Reference Counting) is a type in the std::rc
module that provides shared ownership of a value with reference counting.
use std::rc::Rc;
// Define a struct to represent a person
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
// Creating an Rc containing a Person
let person = Rc::new(Person {
name: String::from("Alice"),
age: 30,
});
// Creating multiple references to the Rc
let reference1 = Rc::clone(&person);
let reference2 = Rc::clone(&person);
// Printing the reference counts
println!("Reference Count after creation: {}", Rc::strong_count(&person));
// Accessing and printing the data through references
println!("Person: {:?}", *reference1);
println!("Person: {:?}", *reference2);
// Dropping one reference
drop(reference1);
println!("Reference Count after dropping one reference: {}", Rc::strong_count(&person));
// Dropping the last reference
drop(reference2);
// The Rc is automatically deallocated when the last reference is dropped
// Attempting to access the data after all references are dropped will result in a compile error
}
Arc (Atomic Reference Counting)
A thread-safe reference-counting pointer.
The type Arc<T>
provides shared ownership of a value of type T
, allocated in the heap.
Arc
is useful when you need shared ownership across multiple threads and want to ensure thread safety. It uses atomic operations for reference counting, making it suitable for concurrent programming.
For Example:
use std::sync::Arc;
use std::thread;
// Define a struct to represent a person
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
// Creating an Arc containing a Person
let person = Arc::new(Person {
name: String::from("Bob"),
age: 25,
});
// Creating a reference to the Arc in a new thread
let thread_person = Arc::clone(&person);
// Spawn a new thread
let handle = thread::spawn(move || {
// Accessing and printing the data through the reference in the new thread
println!("Person in the new thread: {:?}", *thread_person);
});
// Accessing and printing the data in the main thread
println!("Person in the main thread: {:?}", *person);
// Wait for the spawned thread to finish
handle.join().unwrap();
// After the thread has joined, the Arc is automatically deallocated when the last reference is dropped
}
Code Challenge
Modify the code in the editor and try to get Alice's grades.