Rust Structs and Enums
Structs and enums let you design your own types. Together with match, they are the heart of Rust code.
Structs
A struct groups named fields:
Example
struct Student {
name: String,
age: u32,
marks: u32,
}
fn main() {
let mut ali = Student {
name: String::from("Ali"),
age: 19,
marks: 76,
};
ali.marks += 4;
println!("{} ({}) scored {}", ali.name, ali.age, ali.marks);
}Ali (19) scored 80
To change a field, the whole variable must be mut.
Methods with impl
Put methods in an impl block. &self means the method borrows the struct. An associated function without self, like new, is called with ::.
Example
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
fn area(&self) -> u32 {
self.width * self.height
}
fn is_square(&self) -> bool {
self.width == self.height
}
fn double(&mut self) {
self.width *= 2;
self.height *= 2;
}
}
fn main() {
let mut room = Rectangle::new(10, 12);
println!("Area: {}", room.area());
println!("Square? {}", room.is_square());
room.double();
println!("{:?}", room);
}Area: 120
Square? false
Rectangle { width: 20, height: 24 }#[derive(Debug)] lets you print the struct with {:?}.
Enums
An enum is a type that can be one of several variants. Variants can also carry data.
Example
enum Payment {
Cash,
Card(String),
Wallet { app: String, phone: String },
}
fn describe(p: &Payment) -> String {
match p {
Payment::Cash => String::from("Paid in cash"),
Payment::Card(bank) => format!("Paid by {} card", bank),
Payment::Wallet { app, phone } => format!("Paid with {} from {}", app, phone),
}
}
fn main() {
let payments = vec![
Payment::Cash,
Payment::Card(String::from("Meezan")),
Payment::Wallet { app: String::from("JazzCash"), phone: String::from("0300-1234567") },
];
for p in &payments {
println!("{}", describe(p));
}
}Paid in cash Paid by Meezan card Paid with JazzCash from 0300-1234567
match must handle every variant. If you add a new variant later, the compiler tells you every place that needs updating.
Option: A Value or Nothing
Rust has no null. When a value may be missing, it uses the Option enum:
| Variant | Meaning |
|---|---|
Some(value) | There is a value |
None | There is no value |
Example
fn find_student(roll: u32) -> Option<&'static str> {
match roll {
1 => Some("Ayesha"),
2 => Some("Bilal"),
_ => None,
}
}
fn main() {
match find_student(2) {
Some(name) => println!("Found {}", name),
None => println!("No student"),
}
println!("{}", find_student(9).unwrap_or("Unknown"));
}Found Bilal Unknown
Result: Success or Error
Functions that can fail return Result: Ok(value) on success or Err(error) on failure.
Example
fn withdraw(balance: u32, amount: u32) -> Result<u32, String> {
if amount > balance {
Err(String::from("Not enough balance"))
} else {
Ok(balance - amount)
}
}
fn main() {
match withdraw(5000, 2000) {
Ok(left) => println!("Left: Rs {}", left),
Err(e) => println!("Error: {}", e),
}
match withdraw(1000, 3000) {
Ok(left) => println!("Left: Rs {}", left),
Err(e) => println!("Error: {}", e),
}
let n: Result<i32, _> = "abc".parse::<i32>();
println!("{}", n.is_err());
}Left: Rs 3000 Error: Not enough balance true
Inside a function that returns Result, the ? operator passes an error straight back to the caller. It keeps error handling short, and you will see it in almost every real Rust project.
Exercise
Exercise
What does Rust use instead of null for a value that might be missing?
