Skip to main content

Union Types

Declare types that can be one of several variants using the A | B union syntax.

Use Case

You need to model API responses that can be either a success or an error, or identifiers that can be a string or a number.

The Pipeline

# union-types.cst

# Result type: success or error
type Result = { value: Int, status: String } | { error: String, code: Int }

# Flexible identifier
type Identifier = String | Int

# API response variants
type ApiResponse = { data: String, count: Int } | { error: String }

# Processing state variants
type ProcessingState = { pending: Boolean } | { completed: Int } | { failed: String }

# Inputs with union types
in result: Result
in userId: Identifier
in response: ApiResponse
in state: ProcessingState

@example("Operation failed - using fallback")
in fallbackMessage: String

@example(42)
in defaultValue: Int

# Pass through union-typed values
outputResult = result
outputUserId = userId
outputResponse = response
outputState = state

# Regular logic alongside union data
isHighDefault = defaultValue > 50
statusMessage = if (isHighDefault) "High value mode" else "Standard mode"

out outputResult
out outputUserId
out outputResponse
out outputState
out statusMessage
out fallbackMessage

Explanation

DeclarationMeaning
type Result = A | BA value of type Result is either an A or a B
String | IntPrimitive union — value is a string or an integer
{ data: ... } | { error: ... }Record union — value matches one record shape
Three-way unionA | B | C — value matches any of three shapes

Union types are useful for modeling variant data from external APIs where the response shape depends on success or failure.

Pattern Matching

Use match expressions to discriminate between union variants:

type Result = { value: Int, status: String } | { error: String, code: Int }
in response: Result

message = match response {
{ value, status } -> "Success: ${value}",
{ error, code } -> "Error ${code}: ${error}"
}
out message

How It Works

PatternMatches When
{ field1, field2 }Record has exactly these fields
_Any value (wildcard)

The compiler verifies exhaustiveness at compile time. If any union variant is not covered, you get a compile error:

TypeError: Non-exhaustive match on type Result
Missing patterns for: { error: String, code: Int }

Field Binding

Pattern fields become variables in the match body:

type User = { name: String, age: Int } | { error: String }
in user: User

greeting = match user {
{ name, age } -> "Hello ${name}, you are ${age} years old",
{ error } -> "Error: ${error}"
}
out greeting

Wildcard Patterns

Use _ to match any remaining variants:

type State = { active: Boolean } | { pending: Int } | { banned: String }
in state: State

isActive = match state {
{ active } -> active,
_ -> false
}
out isActive
Exhaustiveness Required

All union variants must be covered by patterns. The compiler will error if any variant is unmatched unless you include a wildcard pattern.

Running the Example

Input

{
"result": { "value": 42, "status": "ok" },
"userId": "user-123",
"response": { "data": "hello", "count": 1 },
"state": { "completed": 100 },
"fallbackMessage": "Operation failed - using fallback",
"defaultValue": 42
}

Expected Output

{
"outputResult": { "value": 42, "status": "ok" },
"outputUserId": "user-123",
"outputResponse": { "data": "hello", "count": 1 },
"outputState": { "completed": 100 },
"statusMessage": "Standard mode",
"fallbackMessage": "Operation failed - using fallback"
}

Variations

Scoring result union

type ScoringResult = { score: Int, confidence: Float } | { reason: String }

in input: ScoringResult

out input
tip

Name your union types at the top of the file. type Result = Success | Error is much clearer than repeating { value: Int } | { error: String } everywhere.

Best Practices

  1. Use unions for variant returns — API responses, parsing results, and validation outcomes naturally have multiple shapes
  2. Name your union typestype Result = Success | Error is clearer than inline unions everywhere
  3. Plan for pattern matching — structure your union variants so they'll be easy to discriminate when pattern matching is available