cargo
is used to create, compile, and run Rust programs.
# Create a new Rust project
cargo new <projectname>
# Clean the project
cargo clean
# Compile the project
cargo build
# Run the compiled project
cargo run
rustup
is a toolchain installer for Rust, allowing you to manage Rust versions and associated tools.
A crate is a package of Rust code. It can be a binary or a library.
- The standard library is
std
.- Documentation: doc.rust-lang.org/std/
- Install external crates using:
cargo add <crate_name>
- Find crates at crates.io
- Documentation for crates is available at docs.rs
Use the use
keyword to import a crate.
use rand::{seq::SliceRandom, thread_rng};
- Arrays have a fixed size.
- Vectors can grow and shrink dynamically.
Example:
let array = [1, 2, 3]; // Fixed size
let mut vector = vec![1, 2, 3]; // Dynamic size
vector.push(4); // Now vector is [1, 2, 3, 4]
In Rust, variables are immutable by default. Use mut
to make them mutable.
// Immutable
let numbers = vec![1, 2, 3];
// numbers.push(4); // This will cause an error
// Mutable
let mut numbers = vec![1, 2, 3];
numbers.push(4); // This is allowed
Rust functions return the last expression implicitly, without needing the return
keyword.
fn add(a: i32, b: i32) -> i32 {
a + b // No need for `return`
}
Attributes provide metadata about the code. For example, #[derive(Debug)]
allows you to print debug information.
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 5, y: 10 };
println!("{:?}", point); // Prints: Point { x: 5, y: 10 }
}
A struct
defines a custom data type.
struct Rectangle {
width: u32,
height: u32,
}
-
Associated Functions: Tied to the struct, don't have
self
as a parameter. Use the::
operator.impl Rectangle { fn new(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } } let rect = Rectangle::new(30, 50);
-
Methods: Tied to a specific instance of a struct, have
self
as a parameter. Use the.
operator.impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } let rect = Rectangle { width: 30, height: 50 }; println!("Area: {}", rect.area());
Ownership is a set of rules that governs how a Rust program manages memory. The key principles of ownership are:
- Each value in Rust has a variable that's called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
Example:
{
let s = String::from("hello"); // s is the owner of the String
// s can be used here
} // s goes out of scope and the String is dropped
Borrowing allows you to have references to data without taking ownership. There are two types of borrowing:
- Immutable Borrowing: Allows multiple read-only references.
- Mutable Borrowing: Allows a single read-write reference.
Example:
fn main() {
let s = String::from("hello");
// Immutable borrow
let len = calculate_length(&s);
println!("The length of '{}' is {}.", s, len);
// Mutable borrow
let mut s = String::from("hello");
change(&mut s);
println!("Changed string: {}", s);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn change(s: &mut String) {
s.push_str(", world");
}
Lifetimes are a way of describing the scope for which a reference is valid. They ensure that references do not outlive the data they point to. In C++, you might have a pointer to a variable, and if that variable goes out of scope, the pointer becomes a dangling pointer. Rust's lifetimes ensure that this situation doesn't happen by checking at compile time that all references are valid.
Example:
fn main() {
let string1 = String::from("hello");
let string2 = String::from("world");
let result = longest(&string1, &string2);
println!("The longest string is '{}'", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
Function longest: This function takes two string slices (&str) and returns the longest one. The 'a is a lifetime parameter that tells Rust that the returned reference will be valid as long as both input references are valid.
- Ownership ensures memory safety without a garbage collector.
- Borrowing allows you to use data without taking ownership.
- Lifetimes prevent dangling references by ensuring references are valid.
These concepts are fundamental to Rust's memory safety guarantees and help prevent common bugs such as null pointer dereferencing and data races.
Three ways to define modules in Rust:
-
Create a
mod
in an existing file.You can define a module directly within an existing file using the
mod
keyword. Functions, structs, enums, etc., must havepub
to be accessed outside the module.mod my_module { pub fn say_hello() { println!("Hello from my_module!"); } } fn main() { my_module::say_hello(); }
-
Create a module in a new single file in the same folder.
You can create a new file named
my_module.rs
in the same directory as your main file. Inmy_module.rs
, define your module:// my_module.rs pub fn say_hello() { println!("Hello from my_module!"); }
Then, in your main file, include the module:
// main.rs mod my_module; fn main() { my_module::say_hello(); }
-
Split a module into multiple files.
You can organize a module into multiple files by creating a directory with the module's name and placing a
mod.rs
file inside it. For example, create a directorymy_module
with amod.rs
file:// my_module/mod.rs pub mod greetings; // my_module/greetings.rs pub fn say_hello() { println!("Hello from greetings!"); }
In your main file, include the module:
// main.rs mod my_module; fn main() { my_module::greetings::say_hello(); }