Introduction to Rust Syntax
Rust is a systems programming language that focuses on safety, speed, and concurrency. Its syntax draws inspiration from C and C++ but introduces modern concepts that make it both powerful and expressive. Understanding Rust's syntax is crucial for writing efficient and safe code.
Rust's syntax is designed to be:
- Readable: Clear and intuitive code structure
- Expressive: Complex ideas can be conveyed concisely
- Safe: Syntax encourages memory-safe practices
- Zero-cost: High-level abstractions without runtime overhead
1. Basic Syntax Elements
Comments
// This is a line comment
/// This is a documentation comment for the following item
fn example() {}
/* This is a block comment
that can span multiple lines */
Printing and Output
fn main() {
// Basic printing
println!("Hello, World!"); // With newline
print!("Hello, "); // Without newline
println!("World!");
// Formatted printing
let name = "Alice";
let age = 30;
println!("Name: {}, Age: {}", name, age);
// Debug printing
let numbers = vec![1, 2, 3];
println!("{:?}", numbers); // Debug output
println!("{:#?}", numbers); // Pretty debug output
}
2. Variables and Mutability
Variable Declaration
fn main() {
// Immutable variable (default)
let x = 5;
// x = 6; // This would cause an error!
// Mutable variable
let mut y = 5;
y = 6; // This is fine
// Constants (always immutable)
const MAX_POINTS: u32 = 100_000;
// Type annotations
let z: i32 = 5;
let flo: f64 = 3.14;
let is_true: bool = true;
// Multiple variables
let (a, b, c) = (1, 2, 3);
}
Shadowing
fn main() {
let x = 5;
let x = x + 1; // Shadows previous x
{
let x = x * 2; // Inner scope shadow
println!("Inner x: {}", x); // 12
}
println!("Outer x: {}", x); // 6
}
3. Data Types
Primitive Types
fn main() {
// Integers
let a: i8 = -128; // Signed 8-bit
let b: u8 = 255; // Unsigned 8-bit
let c: i32 = -100; // Signed 32-bit (default)
let d: usize = 100; // Architecture-dependent
// Floating point
let e: f32 = 3.14; // 32-bit float
let f: f64 = 2.71828; // 64-bit float (default)
// Boolean
let g: bool = true;
let h = false;
// Character (4 bytes, Unicode)
let i: char = 'z';
let j = '😻';
// Tuples
let tup: (i32, f64, char) = (500, 6.4, 'q');
let (x, y, z) = tup;
let first = tup.0;
// Arrays (fixed size)
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // Array of 10 zeros
let first = arr[0];
}
4. Functions
Basic Function Syntax
// Function definition
fn add(x: i32, y: i32) -> i32 {
x + y // Expression, no semicolon means return
}
// Function with multiple statements
fn greet(name: &str) -> String {
let greeting = format!("Hello, {}!", name);
greeting // Return value
}
// Unit return type (nothing)
fn do_something() {
println!("Doing something");
}
// Diverging function (never returns)
fn forever() -> ! {
loop {
// infinite loop
}
}
fn main() {
let result = add(5, 3);
println!("Result: {}", result);
let message = greet("Alice");
println!("{}", message);
}
5. Control Flow
Conditional Statements
fn main() {
let number = 6;
// if-else
if number % 4 == 0 {
println!("Divisible by 4");
} else if number % 3 == 0 {
println!("Divisible by 3");
} else {
println!("Not divisible by 4 or 3");
}
// if as an expression
let condition = true;
let value = if condition { 5 } else { 6 };
println!("Value: {}", value);
}
Loops
fn main() {
// Infinite loop
let mut counter = 0;
loop {
counter += 1;
if counter == 5 {
break; // Exit loop
}
if counter == 3 {
continue; // Skip to next iteration
}
println!("Counter: {}", counter);
}
// Returning from loops
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Return value
}
};
println!("Result: {}", result); // 20
// While loop
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
// For loop (range)
for i in 1..5 { // 1 to 4
println!("i: {}", i);
}
for i in 1..=5 { // 1 to 5 inclusive
println!("i: {}", i);
}
// For loop with iterator
let arr = [10, 20, 30];
for element in arr.iter() {
println!("Value: {}", element);
}
// Enumerate
for (index, value) in arr.iter().enumerate() {
println!("Index: {}, Value: {}", index, value);
}
}
6. Ownership and Borrowing
Ownership Rules
fn main() {
// Ownership transfer
let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2
// println!("{}", s1); // Error! s1 no longer valid
// Clone (deep copy)
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2); // Both valid
// Copy types (stack-only)
let x = 5;
let y = x; // x is copied, not moved
println!("x = {}, y = {}", x, y); // Both valid
}
Borrowing and References
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrow s1
println!("Length of '{}' is {}", s1, len);
// Mutable references
let mut s = String::from("hello");
change(&mut s);
println!("Changed: {}", s);
// Multiple references rules
let mut s = String::from("hello");
let r1 = &s; // No problem
let r2 = &s; // No problem
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this
let r3 = &mut s; // No problem
println!("{}", r3);
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but nothing happens
fn change(s: &mut String) {
s.push_str(", world");
}
Slices
fn main() {
let s = String::from("hello world");
// String slices
let hello = &s[0..5];
let world = &s[6..11];
// Shorthand
let slice1 = &s[..5]; // from start to 5
let slice2 = &s[6..]; // from 6 to end
let whole = &s[..]; // entire string
// Array slices
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
// Function with slice parameter
let word = first_word(&s);
println!("First word: {}", word);
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
7. Structs
Defining and Using Structs
// Struct definition
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// Tuple structs
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
// Unit-like struct
struct AlwaysEqual;
fn main() {
// Creating instances
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// Accessing fields
println!("Email: {}", user1.email);
// Mutable instance
let mut user2 = User {
email: String::from("[email protected]"),
username: String::from("anotheruser"),
active: true,
sign_in_count: 1,
};
user2.email = String::from("[email protected]");
// Struct update syntax
let user3 = User {
email: String::from("[email protected]"),
username: String::from("thirduser"),
..user1 // Copy remaining fields from user1
};
// Tuple structs
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// Unit-like struct
let subject = AlwaysEqual;
}
// Function that returns a struct
fn build_user(email: String, username: String) -> User {
User {
email, // Field init shorthand
username, // Field init shorthand
active: true,
sign_in_count: 1,
}
}
Methods and Associated Functions
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Method (takes self)
fn area(&self) -> u32 {
self.width * self.height
}
// Method with parameters
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// Associated function (doesn't take self)
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("Area: {}", rect.area());
let square = Rectangle::square(20); // Called with ::
println!("Square area: {}", square.area());
}
8. Enums and Pattern Matching
Enums
enum IpAddrKind {
V4,
V6,
}
// Enum with data
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
// Enum with different types
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// method body
}
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
let m = Message::Write(String::from("hello"));
m.call();
}
Option Enum
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
// Option vs null-safe operations
let x: i8 = 5;
let y: Option<i8> = Some(5);
// Need to handle Option before using value
match y {
Some(value) => println!("Sum: {}", x + value),
None => println!("No value"),
}
}
Match Control Flow
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
// Match with patterns
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other), // Catch-all
}
// If let syntax (simpler than match for one pattern)
let some_value = Some(3);
if let Some(3) = some_value {
println!("three");
}
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
9. Collections
Vectors
fn main() {
// Creating vectors
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3];
// Adding elements
v.push(5);
v.push(6);
v.push(7);
// Reading elements
let third: &i32 = &v[2];
println!("Third element: {}", third);
match v.get(2) {
Some(third) => println!("Third element: {}", third),
None => println!("No third element"),
}
// Iterating
for i in &v {
println!("{}", i);
}
// Mutable iteration
for i in &mut v {
*i += 50;
}
// Storing different types with enum
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
}
Strings
fn main() {
// Creating strings
let mut s = String::new();
let s2 = "initial contents".to_string();
let s3 = String::from("initial contents");
// Updating strings
let mut s = String::from("foo");
s.push_str("bar"); // push string slice
s.push('!'); // push character
// Concatenation
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1 is moved here
// Format macro
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
// String slicing (careful!)
let hello = "Здравствуйте";
let s = &hello[0..4]; // "Зд" (each char is 2 bytes)
// Iterating over strings
for c in "नमस्ते".chars() {
println!("{}", c);
}
for b in "नमस्ते".bytes() {
println!("{}", b);
}
}
Hash Maps
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// Accessing values
let team_name = String::from("Blue");
let score = scores.get(&team_name);
match score {
Some(s) => println!("Score: {}", s),
None => println!("Team not found"),
}
// Iterating
for (key, value) in &scores {
println!("{}: {}", key, value);
}
// Updating
scores.insert(String::from("Blue"), 25); // Overwrites
// Entry API
scores.entry(String::from("Blue")).or_insert(50);
scores.entry(String::from("Red")).or_insert(50);
// Updating based on old value
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map); // {"world": 2, "hello": 1, "wonderful": 1}
}
10. Error Handling
Panic
fn main() {
// Unrecoverable errors
// panic!("crash and burn");
// Backtrace
let v = vec![1, 2, 3];
// v[99]; // This would panic
}
Result Enum
use std::fs::File;
use std::io::ErrorKind;
use std::io::Read;
fn main() {
// Basic Result handling
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating file: {:?}", e),
},
other_error => panic!("Problem opening file: {:?}", other_error),
},
};
// Using unwrap and expect
let f = File::open("hello.txt").unwrap(); // Panics on error
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
// Propagating errors
fn read_username_from_file() -> Result<String, std::io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
// Using ? operator
fn read_username_from_file_short() -> Result<String, std::io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
// Even shorter
fn read_username_from_file_shorter() -> Result<String, std::io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
11. Generics
Generic Functions
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("Largest number: {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("Largest char: {}", result);
}
Generic Structs
struct Point<T> {
x: T,
y: T,
}
struct PointMixed<T, U> {
x: T,
y: U,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
let mixed = PointMixed { x: 5, y: 4.0 };
}
12. Traits
Defining and Implementing Traits
// Define trait
trait Summary {
fn summarize(&self) -> String;
// Default implementation
fn summarize_author(&self) -> String {
String::from("(Read more...)")
}
}
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
// Traits as parameters
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// Trait bound syntax
fn notify_bound<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
// Multiple trait bounds
fn notify_multi<T: Summary + Display>(item: &T) {}
// Where clause
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
42
}
// Returning types that implement traits
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
}
}
13. Lifetimes
Lifetime Annotations
// Basic lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// Lifetime in structs
struct ImportantExcerpt<'a> {
part: &'a str,
}
// Multiple lifetimes
fn complex<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
// Static lifetime
fn static_lifetime() -> &'static str {
"This string lives forever"
}
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let excerpt = ImportantExcerpt {
part: first_sentence,
};
}
Lifetime Elision Rules
// No explicit lifetimes needed (elision rules apply)
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
// Methods with lifetimes
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
14. Modules and Visibility
Module System
// lib.rs or main.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
// Bringing paths into scope
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
// Using 'use'
hosting::add_to_waitlist();
}
// Re-exporting
pub use crate::front_of_house::hosting;
// Nested paths
use std::{cmp::Ordering, io};
use std::io::{self, Write};
// Glob operator
use std::collections::*;
File Hierarchy Example
// src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
// src/front_of_house.rs
pub mod hosting;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
15. Advanced Features
Closures
fn main() {
// Basic closure
let add_one = |x| x + 1;
println!("{}", add_one(5));
// Closure with type annotations
let expensive_closure = |num: u32| -> u32 {
println!("calculating slowly...");
num * 2
};
// Capturing environment
let x = 4;
let equal_to_x = |z| z == x;
println!("{}", equal_to_x(5));
// Moving ownership into closure
let y = vec![1, 2, 3];
let equal_to_y = move |z| z == y;
// println!("{:?}", y); // y is moved, can't use here
}
Iterators
fn main() {
let v1 = vec![1, 2, 3];
// Creating iterator
let v1_iter = v1.iter();
// Using iterator
for val in v1_iter {
println!("Got: {}", val);
}
// Iterator adapters
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
// Filter
let v1: Vec<i32> = vec![1, 2, 3, 4, 5];
let evens: Vec<_> = v1.into_iter().filter(|x| x % 2 == 0).collect();
assert_eq!(evens, vec![2, 4]);
// Custom iterator
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
let mut counter = Counter::new();
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
}
Smart Pointers
Box
fn main() {
// Box for heap allocation
let b = Box::new(5);
println!("b = {}", b);
// Recursive type with Box
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil))))));
println!("{:?}", list);
}
Rc (Reference Counting)
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
fn main() {
use List::{Cons, Nil};
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
RefCell and Interior Mutability
use std::cell::RefCell;
#[derive(Debug)]
struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
fn send(&self, message: &str) {
self.sent_messages.borrow_mut().push(String::from(message));
// Can also borrow immutably
let messages = self.sent_messages.borrow();
}
}
fn main() {
let m = MockMessenger::new();
m.send("Hello");
m.send("World");
println!("{:?}", m.sent_messages.borrow());
}
Conclusion
Rust's syntax is designed to be expressive while maintaining safety and performance. Key takeaways:
- Ownership and Borrowing are unique to Rust and prevent memory bugs
- Pattern Matching with
matchandif letprovides powerful control flow - Traits enable polymorphic behavior without inheritance
- Generics provide code reuse with zero-cost abstractions
- Lifetimes ensure references are always valid
- The Module System organizes code hierarchically
- Error Handling with
Resultencourages explicit error management
This guide covers the essential syntax you'll need to write Rust code effectively. Practice these concepts and refer to the official Rust documentation for more detailed information.