Skip to content

Enums in Incan

Enums in Incan are algebraic data types (ADTs): a type with a closed set of variants, where each variant can carry different data.

You use enums when a value can be one of a few well-defined shapes and you want the compiler to enforce that you handle every case.

Coming from Python?

Python’s Enum is mainly “named constants”. When Python code needs variants with data it often ends up using class hierarchies and isinstance(...) checks, which are not exhaustive and are easy to break during refactors.

Here’s one representative before/after:

Python (common workaround):

class Shape:
    pass

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

def area(shape: Shape) -> float:
    if isinstance(shape, Circle):
        return 3.14159 * shape.radius * shape.radius
    elif isinstance(shape, Rectangle):
        return shape.width * shape.height
    raise ValueError("unknown shape")

Note: Python 3.10+ has match/case, but it still won’t enforce exhaustiveness the way Incan does.

Incan (enum + exhaustive match):

enum Shape:
    Circle(float)
    Rectangle(float, float)

def area(shape: Shape) -> float:
    match shape:
        Circle(r) => return 3.14159 * r * r
        Rectangle(w, h) => return w * h

If you add a new variant later, the compiler will point you at the match sites that must be updated.

Coming from Rust?

This is the same concept as Rust’s enum + exhaustive match (sum types with payload-carrying variants).

Differences are mostly surface syntax: - Variants are declared in an indented block under enum (no braces/commas). - Construction uses Incan’s syntax (e.g. Status.Active / Message.Move(1, 2)), rather than Rust’s Type::Variant(...).

A motivating example

enum Shape:
    Circle(float)          # radius
    Rectangle(float, float)  # width, height
    Triangle(float, float)   # base, height

def area(shape: Shape) -> float:
    match shape:
        Circle(r) => return 3.14159 * r * r
        Rectangle(w, h) => return w * h
        Triangle(b, h) => return 0.5 * b * h

If you add a new variant later, the compiler will point you at the match sites that must be updated.

Basic syntax

Simple Enum (No Data)

enum Status:
    Pending
    Active
    Completed
    Cancelled

Usage:

status = Status.Active

match status:
    Pending => println("Waiting...")
    Active => println("In progress")
    Completed => println("Done!")
    Cancelled => println("Aborted")

Enum with Data (Variants)

Each variant can carry different types and amounts of data:

enum Message:
    Quit                           # No data
    Move(int, int)                 # Two ints (x, y)
    Write(str)                     # A string
    ChangeColor(int, int, int)     # RGB values

Usage:

msg = Message.Move(10, 20)

match msg:
    Quit => println("Goodbye")
    Move(x, y) => println(f"Moving to ({x}, {y})")
    Write(text) => println(f"Message: {text}")
    ChangeColor(r, g, b) => println(f"RGB({r}, {g}, {b})")

Generic enums

Enums can be generic — parameterized over types:

enum Option[T]:
    Some(T)
    None

enum Result[T, E]:
    Ok(T)
    Err(E)

Note: These are Incan's built-in types for handling optional values and errors.

Custom Generic Enum

enum Tree[T]:
    Leaf(T)
    Node(Tree[T], Tree[T])

# A binary tree of integers
tree = Node(
    Leaf(1),
    Node(Leaf(2), Leaf(3))
)

Pattern matching

The match expression is how you work with enums. It's exhaustive — the compiler ensures you handle all variants.

Basic Match

enum Direction:
    North
    South
    East
    West

def describe(dir: Direction) -> str:
    match dir:
        North => return "Going up"
        South => return "Going down"
        East => return "Going right"
        West => return "Going left"

Extracting Data

enum ApiResponse:
    Success(str, int)        # (data, status_code)
    Error(str)               # error message
    Loading

def handle(response: ApiResponse) -> None:
    match response:
        Success(data, code) =>
            println(f"Got {code}: {data}")
        Error(msg) =>
            println(f"Failed: {msg}")
        Loading =>
            println("Please wait...")

Wildcard Pattern

Use _ to match any remaining variants:

match status:
    Active => println("Working on it")
    _ => println("Not active")  # Matches Pending, Completed, Cancelled

Warning: Wildcards can hide bugs when you add new variants. Prefer explicit matches.

Guards

Add conditions to patterns:

enum Temperature:
    Celsius(float)
    Fahrenheit(float)

def describe(temp: Temperature) -> str:
    match temp:
        Celsius(c) if c > 30 => return "Hot (Celsius)"
        Celsius(c) if c < 10 => return "Cold (Celsius)"
        Celsius(_) => return "Moderate (Celsius)"
        Fahrenheit(f) if f > 86 => return "Hot (Fahrenheit)"
        Fahrenheit(f) if f < 50 => return "Cold (Fahrenheit)"
        Fahrenheit(_) => return "Moderate (Fahrenheit)"

Common patterns

For practical recipes (state machines, commands, error types, expression trees), see:


Enums vs models vs classes

Use Case Enum Model Class
Fixed set of variants
Data that can be one of several shapes
Exhaustive handling required
Simple data container (DTO, config)
Serialization focus (@derive)
Validation and defaults
Inheritance/polymorphism needed
Mutable state with methods
Open extension (new types later)
# Enum: closed set, exhaustive matching
enum PaymentMethod:
    CreditCard(str, str)     # number, expiry
    PayPal(str)              # email
    BankTransfer(str, str)   # account, routing

# Model: data-first, serialization
@derive(Serialize, Deserialize)
model PaymentRequest:
    method: PaymentMethod
    amount: float
    currency: str = "USD"

# Class: behavior-first, inheritance
class PaymentProcessor:
    def process(self, amount: float) -> Result[Receipt, Error]:
        ...

See also: Models and Classes Guide


Built-in enums

Incan provides these enums in the standard library:

Option[T]

Represents an optional value:

enum Option[T]:
    Some(T)
    None

See: Error Handling Guide

Result[T, E]

Represents success or failure:

enum Result[T, E]:
    Ok(T)
    Err(E)

See: Error Handling Guide

Ordering

Comparison result:

enum Ordering:
    Less
    Equal
    Greater

Summary

Concept Description
enum Define a type with fixed variants
Variants Each case of an enum, optionally with data
Generic enum Enum parameterized over types: Option[T]
match Exhaustive pattern matching on enums
Destructuring Extract data from variants: Some(x) =>

Enums are one of Incan's most powerful features — use them for:

  • Modeling states and state machines
  • Error types with rich context
  • Command/message types
  • Any "one of these things" scenario

The compiler guarantees you handle all cases, eliminating a whole class of bugs caused by missing or forgotten cases.


See Also