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

Jitzu
// 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

Jitzu
// 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

Jitzu
// 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:

Jitzu
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

Jitzu
// 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:

Jitzu
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

Jitzu
// 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

Jitzu
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

Jitzu
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

Jitzu
// 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.