What is Rust?
Rust is a multi-paradigm, general-purpose programming language that emphasises performance, type safety, and concurrency. It was originally developed at Mozilla Research and hit version 1.0 in May 2015.
Rust's defining feature is its ownership system โ a set of compile-time rules that guarantee memory safety without needing a garbage collector. This means Rust programs have the raw speed of C and C++, but without the entire class of bugs (buffer overflows, use-after-free, data races) that plague those languages.
Rust has been voted the most loved / admired programming language on the Stack Overflow Developer Survey for nine consecutive years (2016โ2024). It is now used in the Linux kernel, Windows kernel (Microsoft), Android, WebAssembly runtimes, game engines, and more.
Why Choose Rust?
Setup & Installation
Rust is installed via rustup โ the official Rust toolchain installer that manages versions and targets.
Install rustup (all platforms)
# Linux / macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Windows โ download and run rustup-init.exe from rustup.rs
# Or via winget:
winget install Rustlang.RustupVerify & update
rustc --version # rustc 1.78.0 (9b00956e5 2024-04-29)
cargo --version # cargo 1.78.0 (54d8815d0 2024-04-18)
rustup update # keep toolchain currentCreate and run your first project
cargo new hello_rust # scaffolds a new project
cd hello_rust
cargo run # compiles + runs
# โ Hello, world!
cargo build --release # optimised binary in target/release/Hello World
Every Rust program starts with a main() function. The println! macro prints to stdout. Notice the exclamation mark โ macros in Rust end with !.
fn main() {
println!("Hello, World! ๐ฆ");
// Variables are immutable by default
let name = "Rust";
let year: u32 = 2015;
println!("{name} was first released in {year}.");
println!("It has been {} years since release.", 2026 - year);
}Hello, World! ๐ฆRust was first released in 2015.It has been 11 years since release.
Ownership & Borrowing
Ownership is Rust's most unique feature. Every value has one owner; when the owner goes out of scope the value is dropped. References allow you to borrow values without taking ownership โ either immutably (many readers) or mutably (one writer at a time).
fn main() {
// โโ Ownership โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED into s2
// println!("{s1}"); // โ compile error: value used after move
// โโ Cloning (deep copy) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let s3 = String::from("world");
let s4 = s3.clone(); // explicit deep copy
println!("s3={s3}, s4={s4}"); // both valid
// โโ Borrowing (references) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let s5 = String::from("Rust");
let len = calculate_length(&s5); // borrow, not move
println!("Length of '{s5}' is {len}"); // s5 still valid
// โโ Mutable reference โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let mut s6 = String::from("hello");
change(&mut s6);
println!("{s6}"); // "hello, world"
}
fn calculate_length(s: &String) -> usize {
s.len() // s is a reference โ does not take ownership
} // s goes out of scope but doesn't drop the value
fn change(s: &mut String) {
s.push_str(", world");
}s3=world, s4=worldLength of 'Rust' is 4hello, world
Types & Variables
Rust is statically typed with powerful type inference. Variables are immutable by default โ add mut to make them mutable.
fn main() {
// โโ Scalar types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let i: i32 = -42; // signed 32-bit integer
let u: u64 = 1_000_000; // unsigned 64-bit (underscores ok)
let f: f64 = 3.14159; // 64-bit float
let b: bool = true;
let c: char = '๐ฆ'; // Unicode scalar value
// โโ Compound types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let tup: (i32, f64, char) = (500, 6.4, 'z');
let (x, y, z) = tup; // destructuring
println!("Tuple: {x}, {y}, {z}");
let arr: [i32; 5] = [1, 2, 3, 4, 5];
println!("Array[2] = {}", arr[2]);
// โโ String types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let s_lit: &str = "string slice โ borrowed, immutable";
let s_own: String = String::from("owned String โ heap allocated");
let s_fmt: String = format!("{} and {}", s_lit, s_own);
println!("{s_fmt}");
// โโ Collections โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let mut v: Vec<i32> = vec![1, 2, 3];
v.push(4);
v.push(5);
println!("Vec: {:?}", v);
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("Rust", 2015);
map.insert("Go", 2009);
map.insert("TypeScript", 2012);
for (lang, year) in &map {
println!("{lang}: {year}");
}
}Tuple: 500, 6.4, zArray[2] = 3string slice โ borrowed, immutable and owned String โ heap allocatedVec: [1, 2, 3, 4, 5]Rust: 2015Go: 2009TypeScript: 2012
Control Flow
Rust has if, loop, while and for, plus the powerful match expression for pattern matching. In Rust, if and match are expressions โ they return values.
fn main() {
// โโ if / else if / else โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let score = 87;
let grade = if score >= 90 { "A" }
else if score >= 80 { "B" }
else if score >= 70 { "C" }
else { "F" };
println!("Score {score} โ Grade {grade}");
// โโ loop (infinite, with break value) โโโโโโโโโโโโโโโโโโโโโ
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 5 { break counter * 10; }
};
println!("Loop result: {result}"); // 50
// โโ while โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let mut n = 3;
while n > 0 { print!("{n}โฆ "); n -= 1; }
println!("Go! ๐");
// โโ for over a range โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let sum: i32 = (1..=100).sum();
println!("Sum 1โ100 = {sum}"); // 5050
// โโ for over a collection โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let langs = ["Rust", "Go", "C++", "Zig"];
for (i, lang) in langs.iter().enumerate() {
println!("{}: {lang}", i + 1);
}
// โโ match (Rust's powerful pattern matching) โโโโโโโโโโโโโโ
let num = 7;
match num {
1 => println!("one"),
2 | 3 => println!("two or three"),
4..=6 => println!("four to six"),
n if n % 2 == 0 => println!("{n} is even"),
_ => println!("{num} is something else"),
}
}Score 87 โ Grade BLoop result: 503โฆ 2โฆ 1โฆ Go! ๐Sum 1โ100 = 50501: Rust2: Go3: C++4: Zig7 is something else
Structs & Enums
Rust uses structs for data with named fields and enums for types with distinct variants. Both can have methods via impl blocks. Rust's enums are algebraic data types โ variants can carry data, making them far more powerful than C enums.
// โโ Structs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug, Clone)]
struct Point {
x: f64,
y: f64,
}
impl Point {
// Associated function (constructor)
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
// Method โ takes &self
fn distance_from_origin(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
fn translate(&mut self, dx: f64, dy: f64) {
self.x += dx;
self.y += dy;
}
}
// โโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug)]
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Triangle { base: f64, height: f64 },
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Rectangle { width, height } => width * height,
Shape::Triangle { base, height } => 0.5 * base * height,
}
}
}
fn main() {
let mut p = Point::new(3.0, 4.0);
println!("Point: {:?}", p);
println!("Distance: {:.2}", p.distance_from_origin()); // 5.00
p.translate(1.0, 1.0);
println!("After translate: {:?}", p);
let shapes: Vec<Shape> = vec![
Shape::Circle { radius: 5.0 },
Shape::Rectangle { width: 4.0, height: 6.0 },
Shape::Triangle { base: 3.0, height: 8.0 },
];
for shape in &shapes {
println!("{:?} โ area = {:.2}", shape, shape.area());
}
}Point: Point { x: 3.0, y: 4.0 }Distance: 5.00After translate: Point { x: 4.0, y: 5.0 }Circle { radius: 5.0 } โ area = 78.54Rectangle { width: 4.0, height: 6.0 } โ area = 24.00Triangle { base: 3.0, height: 8.0 } โ area = 12.00
Error Handling
Rust has no exceptions. Instead it uses Result<T, E> for recoverable errors and Option<T> for values that may not exist. The ? operator propagates errors up the call stack, keeping error-handling concise without hiding it.
use std::num::ParseIntError;
use std::fmt;
// โโ Custom error type โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug)]
enum AppError {
ParseError(ParseIntError),
NegativeNumber(i32),
TooBig(i32),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::ParseError(e) => write!(f, "Parse error: {e}"),
AppError::NegativeNumber(n) => write!(f, "Negative number: {n}"),
AppError::TooBig(n) => write!(f, "Number too big: {n} (max 100)"),
}
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::ParseError(e)
}
}
fn parse_positive(s: &str) -> Result<i32, AppError> {
let n: i32 = s.parse()?; // ? auto-converts ParseIntError via From
if n < 0 { return Err(AppError::NegativeNumber(n)); }
if n > 100 { return Err(AppError::TooBig(n)); }
Ok(n)
}
fn main() {
let inputs = ["42", "-5", "200", "abc", "99"];
for input in inputs {
match parse_positive(input) {
Ok(n) => println!("โ '{input}' โ {n}"),
Err(e) => println!("โ '{input}' โ {e}"),
}
}
// Option<T> โ value that may or may not exist
let numbers = vec![1, 2, 3, 4, 5];
match numbers.iter().find(|&&x| x > 3) {
Some(n) => println!("First > 3: {n}"),
None => println!("None found"),
}
}โ '42' โ 42โ '-5' โ Negative number: -5โ '200' โ Number too big: 200 (max 100)โ 'abc' โ Parse error: invalid digit found in stringโ '99' โ 99First > 3: 4
Quick Reference Table
| Concept | Rust Syntax | Notes |
|---|---|---|
| Variable (immutable) | let x = 5; | |
| Variable (mutable) | let mut x = 5; | |
| Constant | const MAX: u32 = 100; | |
| Shadow | let x = x + 1; | |
| Function | fn add(a: i32, b: i32) -> i32 { a+b } | |
| Closure | let add = |a, b| a + b; | |
| Reference (borrow) | &value / &mut value | |
| Slice | &arr[1..3] | |
| String literal | &str | |
| Owned string | String::from("hello") | |
| Option | Some(value) / None | |
| Result | Ok(value) / Err(error) | |
| ? operator | let n = s.parse::<i32>()?; | |
| Pattern match | match x { 1 => .., _ => .. } | |
| Struct | struct Point { x: f64, y: f64 } | |
| Enum variant w/ data | enum Shape { Circle { r: f64 } } | |
| Trait | trait Animal { fn speak(&self); } | |
| impl block | impl Point { fn new() -> Self {} } | |
| Generic | fn largest<T: PartialOrd>(v: &[T]) | |
| Lifetime | 'a โ fn f<'a>(x: &'a str) | |
| Vec | let v: Vec<i32> = vec![1,2,3]; | |
| HashMap | HashMap::new(); map.insert(k, v); | |
| Async/await | async fn f() -> T { โฆ await } | |
| Macro | println!(), vec![], format!() | |
| Cargo commands | new / build / run / test / fmt / clippy |
What to Learn Next
Ready for the full tutorial?
10 hands-on examples โ ownership, traits, iterators, async, error handling and more.
Start Rust Tutorial โ 10 Examples โKeep Learning
Also in Systems & Low-Level
C++
The language Rust was designed to replace for safe systems code.
C
The foundation of systems programming โ close to the metal.
Go
Fast, simple systems language with garbage collection.
Assembly
The lowest-level systems language โ closest to the hardware.