Pattern Matching
Pattern matching is one of Jitzu’s most powerful features. Combined with union types and the built-in Result/Option system, it lets you handle different cases elegantly and safely.
Union Types
Union types allow a value to be one of several variants, providing type-safe alternatives to traditional enums.
Defining Union Types
// Basic union type
union Pet {
Fish,
Cat(String), // Cat with name
Dog(String, Int), // Dog with name and age
Bird(String, Bool), // Bird with name and can_talk
None,
}
// Union for error handling
union FileResult {
Success(String),
NotFound,
PermissionDenied,
InvalidFormat(String),
}Creating Union Instances
// Creating union instances
let my_pet = Pet.Cat("Whiskers")
let family_dog = Pet.Dog("Rex", 5)
let goldfish = Pet.Fish
let no_pet = Pet.None
// Result instances
let success = FileResult.Success("File content here")
let error = FileResult.InvalidFormat("Not a valid JSON file")Match Expressions
Match expressions provide exhaustive pattern matching over union types.
Basic Pattern Matching
// Basic match expression
let pet = Pet.Cat("Whiskers")
match pet {
Pet.Fish => print("Fish don't need names"),
Pet.Cat(name) => print(`Hello cat {name}`),
Pet.Dog(name, age) => print(`Dog {name} is {age} years old`),
Pet.Bird(name, can_talk) => {
if can_talk {
print(`{name} the talking bird`)
} else {
print(`{name} the quiet bird`)
}
},
Pet.None => print("No pets"),
}
// Match expressions return values
let pet_description = match pet {
Pet.Fish => "A silent swimmer",
Pet.Cat(name) => `A cat named {name}`,
Pet.Dog(name, age) => `A {age}-year-old dog named {name}`,
Pet.Bird(name, _) => `A bird named {name}`,
Pet.None => "No pet"
}Wildcard Patterns
Use _ to ignore values you don’t need:
match pet {
Pet.Dog(name, _) => print(`Dog: {name}`), // Ignore age
_ => print("Not a dog"), // Match anything else
}Result Types
Result types are a built-in union for handling operations that can succeed or fail.
Working with Results
// Function returning Result
fun divide(a: Double, b: Double): Result<Double, String> {
if b == 0.0 {
Err("Division by zero")
} else {
Ok(a / b)
}
}
// Pattern matching Results
let result = divide(10.0, 2.0)
match result {
Ok(value) => print(`Result: {value}`),
Err(error) => print(`Error: {error}`)
}The Try Operator
Use try to propagate errors without nested match statements:
fun safe_sqrt(x: Double): Result<Double, String> {
if x < 0.0 {
Err("Cannot take square root of negative number")
} else {
Ok(x)
}
}
// Using try for early return on errors
fun complex_calculation(a: Double, b: Double, c: Double): Result<Double, String> {
let step1 = try divide(a, b) // Returns Err early if division fails
let step2 = try safe_sqrt(step1) // Returns Err early if sqrt fails
let step3 = try divide(step2, c) // Returns Err early if division fails
Ok(step3)
}
// Without try (more verbose)
fun complex_calculation_verbose(a: Double, b: Double, c: Double): Result<Double, String> {
match divide(a, b) {
Ok(step1) => {
match safe_sqrt(step1) {
Ok(step2) => divide(step2, c),
Err(e) => Err(e)
}
},
Err(e) => Err(e)
}
}Option Types
Option types handle nullable values safely.
Option Patterns
// Function returning Option
fun find_user(users: User[], id: Int): Option<User> {
for user in users {
if user.id == id {
return Some(user)
}
}
None
}
// Pattern matching Options
match find_user(users, 1) {
Some(user) => print(`Found user: {user.name}`),
None => print("User not found")
}Best Practices
- Cover all cases - Match expressions should be exhaustive
- Use wildcard patterns - Use
_for cases you don’t care about - Prefer Result/Option - Use them instead of null checks for safer code
- Use try - The try operator keeps error propagation clean and readable
Common Errors
Mismatched branch types
let label = match status {
Ok(v) => `Success: {v}`,
Err(e) => 42, // returns Int, not String
}
// Error: Couldn't resolve match to a single return type
// Fix: all branches must return the same type
let label = match status {
Ok(v) => `Success: {v}`,
Err(e) => `Error: {e}`,
}Destructuring a type without a constructor
type Config { pub path: String }
match config {
Config(p) => print(p),
}
// Error: Type Config does not have a constructor
// Fix: use field access instead of destructuring
print(config.path)Ambiguous type name
// When two packages export the same type name
let s = JsonSerializer {}
// Error: Type 'JsonSerializer' is ambiguous. Did you mean:
// - System.Text.Json.JsonSerializer
// - Newtonsoft.Json.JsonSerializer
// Fix: use the fully qualified name
let s = System.Text.Json.JsonSerializer {}Pattern matching makes Jitzu code more expressive and safer by ensuring all cases are handled. It’s particularly powerful when combined with union types and the Result/Option system. Next, explore .NET Interop to learn how to use NuGet packages and call .NET methods.