The match Control Flow Construct
The match expression lets you compare a value against a series of patterns and execute code based on which pattern matches. It is the primary way to work with enums in Move.
Basic Match
module my_addr::matcher {
enum Coin has copy, drop {
Penny,
Nickel,
Dime,
Quarter,
}
fun value_in_cents(coin: Coin): u64 {
match (coin) {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
Matching with Data
When a variant carries data, you can bind the data to variables in the pattern:
enum Shape has copy, drop {
Circle { radius: u64 },
Rectangle { width: u64, height: u64 },
}
fun area(shape: Shape): u64 {
match (shape) {
Shape::Circle { radius } => {
// Approximate: 3 * r^2
3 * radius * radius
},
Shape::Rectangle { width, height } => {
width * height
},
}
}
Matching Positional Variants
enum Result has copy, drop {
Ok(u64),
Err(u64),
}
fun unwrap_or_default(result: Result): u64 {
match (result) {
Result::Ok(value) => value,
Result::Err(_) => 0,
}
}
Exhaustiveness
The Move compiler requires that match expressions are exhaustive -- every possible variant must be handled. This prevents bugs where a case is accidentally forgotten.
// This would NOT compile -- missing Coin::Quarter:
// fun broken(coin: Coin): u64 {
// match (coin) {
// Coin::Penny => 1,
// Coin::Nickel => 5,
// Coin::Dime => 10,
// }
// }
Multi-Line Match Arms
Match arms can contain multiple statements within braces:
fun describe(shape: Shape): vector<u8> {
match (shape) {
Shape::Circle { radius } => {
if (radius > 100) {
b"large circle"
} else {
b"small circle"
}
},
Shape::Rectangle { width, height } => {
if (width == height) {
b"square"
} else {
b"rectangle"
}
},
}
}
Match and References
You can match on references to avoid consuming the value:
fun is_circle(shape: &Shape): bool {
match (shape) {
Shape::Circle { .. } => true,
_ => false,
}
}
Practical Example: State Machine
Enums and match are ideal for implementing state machines in smart contracts:
module my_addr::auction {
use std::signer;
enum AuctionState has copy, drop, store {
Open { highest_bid: u64, highest_bidder: address },
Closed,
Settled { winner: address, amount: u64 },
}
struct Auction has key {
state: AuctionState,
seller: address,
}
public fun process_bid(auction: &mut Auction, bidder: address, amount: u64) {
auction.state = match (auction.state) {
AuctionState::Open { highest_bid, highest_bidder } => {
if (amount > highest_bid) {
AuctionState::Open {
highest_bid: amount,
highest_bidder: bidder,
}
} else {
AuctionState::Open { highest_bid, highest_bidder }
}
},
AuctionState::Closed => abort 1, // Cannot bid on closed auction
AuctionState::Settled { .. } => abort 2, // Already settled
};
}
}
Best Practices
- Always handle all variants: The compiler enforces this, which prevents missing-case bugs.
- Use
_for unused bindings: If you don't need a value, use_to ignore it. - Keep match arms simple: If a match arm is complex, extract the logic into a separate function.
- Use enums for state machines: The combination of enums and match naturally models state transitions.