Structs and Enums
On this page 21
Home provides powerful data structures through structs and enums, supporting methods, generics, and algebraic data types.
Structs
Structs group related data together:
Basic Struct Definition
struct Point {
x: int,
y: int
}
struct User {
id: i64,
name: string,
email: string
}
Creating Struct Instances
let origin = Point { x: 0, y: 0 }
let user = User {
id: 1,
name: "Alice",
email: "alice@example.com"
}
// Field shorthand when variable names match
let name = "Bob"
let email = "bob@example.com"
let bob = User { id: 2, name, email }
Accessing Fields
let p = Point { x: 10, y: 20 }
print("x = {p.x}, y = {p.y}")
// Nested access
struct Rectangle {
top_left: Point,
bottom_right: Point
}
let rect = Rectangle {
top_left: Point { x: 0, y: 0 },
bottom_right: Point { x: 100, y: 50 }
}
print("Width: {rect.bottom_right.x - rect.top_left.x}")
Mutable Structs
let mut point = Point { x: 0, y: 0 }
point.x = 10
point.y = 20
Methods
Add behavior to structs with impl blocks:
struct Rectangle {
width: int,
height: int
}
impl Rectangle {
// Associated function (constructor)
fn new(width: int, height: int): Rectangle {
Rectangle { width, height }
}
// Method (takes self)
fn area(self): int {
self.width _ self.height
}
fn perimeter(self): int {
2 _ (self.width + self.height)
}
// Mutable method
fn scale(mut self, factor: int) {
self.width _= factor
self.height _= factor
}
fn is_square(self): bool {
self.width == self.height
}
}
// Usage
let rect = Rectangle.new(10, 5)
print("Area: {rect.area()}") // 50
print("Perimeter: {rect.perimeter()}") // 30
print("Is square: {rect.is_square()}") // false
let mut square = Rectangle.new(5, 5)
square.scale(2)
print("Scaled: {square.width}x{square.height}") // 10x10
Self References
impl Point {
// Immutable borrow of self
fn distance_from_origin(&self): float {
((self.x _ self.x + self.y _ self.y) as float).sqrt()
}
// Mutable borrow of self
fn translate(&mut self, dx: int, dy: int) {
self.x += dx
self.y += dy
}
// Takes ownership of self
fn into_tuple(self): (int, int) {
(self.x, self.y)
}
}
Generic Structs
Structs can be parameterized over types:
struct Pair<T> {
first: T,
second: T
}
struct Container<T, U> {
key: T,
value: U
}
let pair = Pair { first: 1, second: 2 }
let container = Container { key: "name", value: 42 }
Generic Methods
struct Stack<T> {
items: []T
}
impl<T> Stack<T> {
fn new(): Stack<T> {
Stack { items: [] }
}
fn push(mut self, item: T) {
self.items.append(item)
}
fn pop(mut self): Option<T> {
self.items.pop()
}
fn is_empty(self): bool {
self.items.len() == 0
}
fn len(self): int {
self.items.len()
}
}
let mut stack = Stack<int>.new()
stack.push(1)
stack.push(2)
stack.push(3)
let top = stack.pop() // Some(3)
Enums
Enums define types with a fixed set of variants:
Simple Enums
enum Color {
Red,
Green,
Blue
}
let color = Color.Red
match color {
Color.Red => print("red"),
Color.Green => print("green"),
Color.Blue => print("blue")
}
Enums with Data
enum Message {
Quit,
Move(x: int, y: int),
Write(string),
ChangeColor(int, int, int)
}
let msg = Message.Move(10, 20)
match msg {
Message.Quit => print("Quit"),
Message.Move(x, y) => print("Move to ({x}, {y})"),
Message.Write(text) => print("Write: {text}"),
Message.ChangeColor(r, g, b) => print("Color: rgb({r}, {g}, {b})")
}
Generic Enums
enum Option<T> {
Some(T),
None
}
enum Result<T, E> {
Ok(T),
Err(E)
}
let value: Option<int> = Option.Some(42)
let result: Result<string, Error> = Result.Ok("success")
Enum Methods
enum Option<T> {
Some(T),
None
}
impl<T> Option<T> {
fn is_some(self): bool {
match self {
Option.Some(_) => true,
Option.None => false
}
}
fn is_none(self): bool {
!self.is_some()
}
fn unwrap(self): T {
match self {
Option.Some(value) => value,
Option.None => panic("Called unwrap on None")
}
}
fn unwrap_or(self, default: T): T {
match self {
Option.Some(value) => value,
Option.None => default
}
}
fn map<U>(self, f: fn(T): U): Option<U> {
match self {
Option.Some(value) => Option.Some(f(value)),
Option.None => Option.None
}
}
}
Algebraic Data Types
Combine enums and structs for powerful type modeling:
// A linked list
enum List<T> {
Cons(T, Box<List<T>>),
Nil
}
// A binary tree
enum Tree<T> {
Node(T, Box<Tree<T>>, Box<Tree<T>>),
Leaf
}
// Expression AST
enum Expr {
Number(int),
Add(Box<Expr>, Box<Expr>),
Subtract(Box<Expr>, Box<Expr>),
Multiply(Box<Expr>, Box<Expr>),
Divide(Box<Expr>, Box<Expr>)
}
fn evaluate(expr: Expr): int {
match expr {
Expr.Number(n) => n,
Expr.Add(a, b) => evaluate(_a) + evaluate(_b),
Expr.Subtract(a, b) => evaluate(_a) - evaluate(_b),
Expr.Multiply(a, b) => evaluate(_a) _ evaluate(_b),
Expr.Divide(a, b) => evaluate(_a) / evaluate(*b)
}
}
Tuple Structs
Structs with unnamed fields:
struct Point2D(int, int)
struct Color(u8, u8, u8)
let point = Point2D(10, 20)
let red = Color(255, 0, 0)
print("x = {point.0}, y = {point.1}")
print("R = {red.0}")
Unit Structs
Structs with no fields:
struct Marker
impl Marker {
fn describe() {
print("I'm a marker type")
}
}
Struct Update Syntax
Create a new struct based on an existing one:
let user1 = User {
id: 1,
name: "Alice",
email: "alice@example.com"
}
// Create user2 with same email but different name
let user2 = User {
id: 2,
name: "Bob",
..user1 // Copy remaining fields from user1
}
Visibility
Control access to struct fields:
struct Config {
pub name: string, // Public
secret_key: string // Private (default)
}
impl Config {
pub fn new(name: string, key: string): Config {
Config { name, secret_key: key }
}
pub fn get_name(self): string {
self.name
}
// Private method
fn validate(self): bool {
self.secret_key.len() >= 16
}
}
Pattern Matching with Structs
Destructure structs in patterns:
struct Point { x: int, y: int }
let point = Point { x: 10, y: 20 }
match point {
Point { x: 0, y: 0 } => print("origin"),
Point { x: 0, y } => print("on y-axis at {y}"),
Point { x, y: 0 } => print("on x-axis at {x}"),
Point { x, y } if x == y => print("on diagonal"),
Point { x, y } => print("at ({x}, {y})")
}
// Destructuring in let
let Point { x, y } = point
print("x = {x}, y = {y}")
// Ignoring fields
let Point { x, .. } = point
print("x = {x}")
Next Steps
- Traits - Define shared behavior for types
- Error Handling - Using Result and Option types
- Memory Safety - Ownership and borrowing