Getting Started with Rust
Post on March 15, 2024
Rust is a systems programming language that combines low-level control with high-level ergonomics. It guarantees memory safety without a garbage collector, making it ideal for performance-critical applications.
Why Rust?
Key advantages:
- Memory safety without garbage collection
- Zero-cost abstractions for performance
- Fearless concurrency with compile-time checks
- Great tooling with Cargo and rustfmt
- Growing ecosystem for web, embedded, and systems work
Common use cases:
- Web tooling and build systems (like Rolldown!)
- Command-line tools
- WebAssembly modules
- Embedded systems
- Network services
Installation
Install Rust with rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
This installs:
rustc(the Rust compiler)
cargo(package manager and build tool)
rustup(toolchain manager)
Verify installation
rustc --version cargo --version
Update Rust
rustup update
Your First Rust Program
Create a new project:
cargo new hello_rust cd hello_rust
This creates:
hello_rust/ ├── Cargo.toml # Project metadata and dependencies └── src/ └── main.rs # Your code
fn main() { println!("Hello, world!"); }
Run it:
cargo run
Rust Fundamentals
Variables and Mutability
Variables are immutable by default:
let x = 5; // x = 6; // Error: cannot assign twice to immutable variable let mut y = 5; y = 6; // OK: y is mutable
Data Types
// Integers let a: i32 = 42; // 32-bit signed let b: u64 = 100; // 64-bit unsigned // Floats let c: f64 = 3.14; // Boolean let d: bool = true; // Characters let e: char = '🦀'; // Tuples let tuple: (i32, f64, char) = (500, 6.4, 'x'); // Arrays let array: [i32; 3] = [1, 2, 3];
Functions
fn add(x: i32, y: i32) -> i32 { x + y // No semicolon = return value } fn main() { let result = add(5, 3); println!("Result: {}", result); }
Control Flow
// if expressions let number = 6; if number % 2 == 0 { println!("Even"); } else { println!("Odd"); } // loop loop { println!("Forever!"); break; // until we break } // while let mut n = 0; while n < 5 { n += 1; } // for for i in 0..5 { println!("{}", i); }
Ownership: Rust's Superpower
Ownership is Rust's most distinctive feature—it ensures memory safety without a garbage collector.
The Rules
- Each value has a single owner
- When the owner goes out of scope, the value is dropped
- Values can be moved or borrowed, but not both
Moving Values
let s1 = String::from("hello"); let s2 = s1; // s1 is moved to s2 // println!("{}", s1); // Error: s1 is no longer valid println!("{}", s2); // OK
Borrowing
fn calculate_length(s: &String) -> usize { s.len() // s is borrowed, not owned } fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // Borrow s1 println!("'{}' has length {}", s1, len); // s1 still valid }
Mutable Borrowing
fn append_world(s: &mut String) { s.push_str(", world!"); } fn main() { let mut s = String::from("hello"); append_world(&mut s); println!("{}", s); // "hello, world!" }
Key rule: You can have either one mutable reference OR multiple immutable references, but not both.
Structs and Enums
Structs
struct User { username: String, email: String, active: bool, } impl User { fn new(username: String, email: String) -> User { User { username, email, active: true, } } fn deactivate(&mut self) { self.active = false; } } fn main() { let mut user = User::new( String::from("alice"), String::from("alice@example.com") ); user.deactivate(); }
Enums and Pattern Matching
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), } fn process_message(msg: Message) { match msg { Message::Quit => println!("Quit"), Message::Move { x, y } => println!("Move to ({}, {})", x, y), Message::Write(text) => println!("Write: {}", text), } }
Option and Result
Rust has no
null. Instead, use Option:fn divide(a: i32, b: i32) -> Option<i32> { if b == 0 { None } else { Some(a / b) } } match divide(10, 2) { Some(result) => println!("Result: {}", result), None => println!("Cannot divide by zero"), }
For error handling, use
Result:use std::fs::File; use std::io::ErrorKind; fn open_file() -> Result<File, std::io::Error> { File::open("file.txt") } match open_file() { Ok(file) => println!("Opened file"), Err(error) => match error.kind() { ErrorKind::NotFound => println!("File not found"), other => println!("Error: {:?}", other), }, }
Error Handling Shortcuts
use std::fs::File; // ? operator propagates errors fn read_file() -> Result<String, std::io::Error> { let mut file = File::open("file.txt")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }
Collections
Vector
let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); // or let v = vec![1, 2, 3]; for i in &v { println!("{}", i); }
HashMap
use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Red"), 50); match scores.get("Blue") { Some(score) => println!("Score: {}", score), None => println!("No score"), }
Common Cargo Commands
cargo new my-project # Create new project cargo build # Build in debug mode cargo build --release # Build optimized cargo run # Build and run cargo test # Run tests cargo check # Check without building cargo clippy # Run linter cargo fmt # Format code
Next Steps
Essential reading:
- The Rust Book (start here!)
- Rustlings (interactive exercises)
Intermediate topics:
- Lifetimes and advanced borrowing
- Traits and generics
- Concurrency with threads and async/await
- Macros
- Unsafe Rust
Practice projects:
- Build a CLI tool
- Implement common data structures
- Create a simple web server
- Contribute to open source Rust projects
Rust has a steep learning curve, but the compiler is your teacher. Read error messages carefully—they're remarkably helpful. Welcome to the Rust community! 🦀