- Variable Declaration
- Basic Data Types
- Structs
- Implementations (impls)
- Enums
- Pattern Matching
- Vectors
- Ownership and Borrowing
- Traits
- Error Handling
- Closures
- Iterators
- Generics
- Lifetimes
- Smart Pointers
- Concurrency
- Modules
In Rust, variables are immutable by default, promoting safer and more predictable code.
let x = 5; // Immutable variable
let mut y = 10; // Mutable variable
const MAX_POINTS: u32 = 100_000; // Constantletdeclares an immutable variable.let mutdeclares a mutable variable that can be changed later.constdeclares a constant, which must have a type annotation and be assigned a constant expression.
Rust has several basic data types to represent numbers, characters, and strings.
let a: u32 = 42; // Unsigned 32-bit integer
let b: i64 = -100; // Signed 64-bit integer
let c: f32 = 3.14; // 32-bit floating-point number
let d: bool = true; // Boolean
let e: char = 'x'; // Character
let f: &str = "Hello, World!"; // String slice
let g: String = String::from("Hello, World!"); // Owned Stringu32: Unsigned 32-bit integer (0 to 4,294,967,295)i64: Signed 64-bit integer (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)f32: 32-bit floating-point numberbool: Boolean (true or false)char: Unicode scalar value&str: String slice (borrowed string)String: Owned string (can be modified)
Structs are custom data types that group related data together.
struct Person {
name: String,
age: u32,
}
// Creating an instance of a struct
let person = Person {
name: String::from("Alice"),
age: 30,
};
// Accessing struct fields
println!("Name: {}", person.name); // Name: Alice
println!("Age: {}", person.age); // Age: 30
// Mutable struct instance
let mut mutable_person = Person {
name: String::from("Bob"),
age: 25,
};
mutable_person.age = 26; // Updating age field
// Tuple Struct
struct Point(i32, i32);
let point = Point(10, 20);
// Unit Struct (marker)
struct Unit;
let unit = Unit;- Regular structs have named fields.
- Tuple structs have unnamed fields, accessed by index.
- Unit structs have no fields and are often used as markers.
Implementations allow you to define methods and associated functions for structs.
impl Person {
// Associated function (constructor)
fn new(name: &str, age: u32) -> Person {
Person {
name: String::from(name),
age,
}
}
// Method
fn say_hello(&self) {
println!("Hello, my name is {}", self.name);
}
// Mutable method
fn have_birthday(&mut self) {
self.age += 1;
}
}
// Using the `new` constructor and methods
let mut alice = Person::new("Alice", 30);
alice.say_hello(); // Hello, my name is Alice
alice.have_birthday();
println!("Alice's new age: {}", alice.age); // Alice's new age: 31- Associated functions (like
new) are called on the struct itself. - Methods take
&selfor&mut selfas the first parameter and are called on instances.
Enums allow you to define a type that can be one of several variants.
enum Direction {
North,
South,
East,
West,
}
let direction = Direction::East;
enum OptionalInt {
Value(i32),
Nothing,
}
let x = OptionalInt::Value(42);
let y = OptionalInt::Nothing;- Enums can have variants with no data, like
Direction. - Enums can also have variants with associated data, like
OptionalInt.
Pattern matching is a powerful feature in Rust for destructuring and matching against patterns.
match direction {
Direction::North => println!("Going North"),
Direction::South => println!("Going South"),
Direction::East => println!("Going East"),
Direction::West => println!("Going West"),
}
match x {
OptionalInt::Value(n) => println!("Value: {}", n),
OptionalInt::Nothing => println!("Nothing"),
}matchexpressions must be exhaustive, covering all possible cases.- Pattern matching can destructure enums, tuples, and structs.
Vectors are dynamic arrays that can grow or shrink in size.
let mut vec = Vec::new();
vec.push(1);
vec.push(2);
vec.push(3);
let first = vec[0]; // 1
// Slices
let slice = &vec[1..3]; // Creates a slice of the vector from index 1 to 2
println!("Slice: {:?}", slice); // Slice: [2, 3]
// Iterating over vectors
for x in &vec {
println!("{}", x);
}- Vectors can store elements of the same type.
- Slices are references to a contiguous sequence of elements in a collection.
- Vectors can be iterated over using a
forloop.
Rust's ownership system is a key feature that ensures memory safety without a garbage collector.
let s1 = String::from("hello");
let s2 = s1; // s1 is invalid after this line
let s3 = s2.clone(); // Create a clone
let s4 = String::from("world");
let s5 = &s4; // Borrowing reference
println!("{}, {}", s5, s4); // world, world- When a value is assigned to another variable, ownership is transferred (moved).
- To create a deep copy, use the
clone()method. - References allow you to refer to a value without taking ownership.
Traits define shared behavior across types.
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
headline: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} - {}", self.headline, self.content.split_whitespace().take(5).collect::<Vec<&str>>().join(" "))
}
}
let article = Article {
headline: String::from("Rust Cheatsheet"),
content: String::from("This is a cheatsheet for the Rust programming language."),
};
println!("{}", article.summarize());- Traits are similar to interfaces in other languages.
- Types can implement multiple traits.
- Trait methods can have default implementations.
Rust uses the Result and Option types for error handling.
// Using Result
fn divide(x: f64, y: f64) -> Result<f64, String> {
if y == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(x / y)
}
}
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Using Option
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
match find_user(1) {
Some(name) => println!("User found: {}", name),
None => println!("User not found"),
}Result<T, E>represents either success (Ok(T)) or failure (Err(E)).Option<T>represents either some value (Some(T)) or no value (None).
Closures are anonymous functions that can capture their environment.
let add_one = |x: i32| x + 1;
println!("5 + 1 = {}", add_one(5));- Closures can be used as arguments to higher-order functions.
- They can capture variables from their surrounding scope.
Iterators allow you to process sequences of elements.
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
println!("Doubled numbers: {:?}", doubled);- Iterators are lazy and don't compute values until consumed.
- Many methods like
map,filter, andfoldare available on iterators.
Generics allow you to write flexible, reusable code that works with multiple types.
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
let numbers = vec![34, 50, 25, 100, 65];
println!("The largest number is {}", largest(&numbers));<T: PartialOrd>specifies that the typeTmust implement thePartialOrdtrait.- Generics can be used with functions, structs, and enums.
Lifetimes ensure that references are valid for as long as they're used.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
let result;
{
let string1 = String::from("short");
let string2 = String::from("longer");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);- Lifetimes are denoted with an apostrophe, like
'a. - They ensure that references don't outlive the data they refer to.
Smart pointers are data structures that act like pointers but have additional metadata and capabilities.
use std::rc::Rc;
let s = Rc::new(String::from("Hello, Rust!"));
let s1 = Rc::clone(&s);
let s2 = Rc::clone(&s);
println!("{} has {} strong references", s, Rc::strong_count(&s));Rc<T>(Reference Counted) allows multiple ownership of the same data.- It keeps track of the number of references to a value.
Rust provides tools for safe and efficient concurrent programming.
use std::thread;
use std::time::Duration;
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
handle.join().unwrap();thread::spawncreates a new thread.handle.join()waits for the spawned thread to finish.
Modules help organize and encapsulate code.
mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
use math::add;
println!("5 + 3 = {}", add(5, 3));moddefines a module.pubmakes items public and accessible outside the module.usebrings items into scope.
This cheatsheet covers many of the fundamental concepts in Rust. Each section provides code examples and explanations to help you understand and use these features effectively in your Rust programs.