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

  1. Each value has a single owner
  1. When the owner goes out of scope, the value is dropped
  1. 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:
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! 🦀