Enums allow you to define a type with a fixed set of named values. They’re useful for representing states, options, or any situation where a variable should only hold one of several predefined, immutable, and distinct values.
Defining Enums
We can declare an enum using the enum keyword followed by the name of the enum and a block containing its members. Each member must be a valid and unique identifier within the enumeration and can be given their own line or be separated by commas.
// Single-line enum declaration
enum Color { Red, Green, Blue }
// Multi-line enum declaration
enum Color {
Red
Green
Blue
}Using Enums
Each variant is a distinct value of the enum type. You can access variants using dot notation used for member access. When assigning an enum to value that expects an enum type, you may omit the enum name prefix.
var favoriteColor = Color.Red
favoriteColor = .Green // shorthand, same as Color.GreenEnums are commonly used with pattern matching to handle different cases. When used in a when statement, the enum variants can be matched directly. If you don’t handle all possible variants, you must include an else case to cover any unhandled variants, otherwise an exhaustiveness error will occur.
enum Direction {
North
South
East
West
}
func getOffset(dir Direction) vec2 {
when dir {
Direction.North { return vec2(0, -1) }
Direction.South { return vec2(0, 1) }
Direction.East { return vec2(1, 0) }
Direction.West { return vec2(-1, 0) }
}
}Explicit Values
By default, enum variants are assigned sequential integer values starting from 0 and will size itself to the smallest possible integer that can hold all values. You can explicitly set these values.
enum Status {
Idle = 0
Running = 1
Paused = 2
Stopped = 3
}Explicit values can be any integer type. They don’t need to be sequential.
enum FilePermission {
Read = 4
Write = 2
Execute = 1
}This is useful when enum values correspond to flags, protocol values, or other externally defined constants.
Auto-Incrementing Values
When you set an explicit value, subsequent variants without explicit values auto-increment from that point.
enum Token {
Illegal = 0
EOF = 1
// These auto-increment from 2
Ident
Number
String
// Explicitly set to 100
Plus = 100
// These continue from 101
Minus
Star
Slash
}In this example, Ident is 2, Number is 3, String is 4, Plus is 100, Minus is 101, and so on.
Accessing Enum Values
To get the underlying integer value of an enum variant, use a type cast targeting the desired integer type.
enum Priority {
Low = 1
Medium = 5
High = 10
}
var p = Priority.High
var value = i32(p) // 10You can also convert integers back to enum values, though this may produce a compile-time or runtime error for the following reasons:
- The integer does not correspond to any defined enum variant.
- The integer is out of range for the enum’s underlying type.
- The integer is not a valid representation of the enum’s type.
- Two or more variants share the same assigned value.
var priority = try Priority(5) // Priority.Medium
var invalid = try Priority(3) // Error: no variant with value 3Enum Comparison
Enums can be compared using standard comparison operators.
var current = Status.Running
var target = Status.Paused
if current == target {
c.printf("Status matches\n")
}
if current != Status.Stopped {
c.printf("Still active\n")
}Type Safety
Enums provide type safety. You cannot accidentally use a value from one enum type where another is expected without an explicit cast. This helps prevent bugs and makes your code more self-documenting.
enum Color {
Red
Green
Blue
}
enum Size {
Small
Medium
Large
}
var color = Color.Red
var size = Size.Small
// This will not compile
if color == size {
// Error: cannot compare Color with Size
}
if i8(color) == i8(size) {
// This is allowed
}Enums vs Unions
Conjure distinguishes between enums (constant, value-only variants) and unions (variants that can hold associated data). If you need to attach data to your variants, use a union instead.
// Enum: just the variant name
enum SimpleState {
Active
Inactive
}
// Union: variants can hold data
union Maybe[T] {
None
Some T
}See the Unions page for more information about tagged unions with associated data.
Iterating Over Enum Values
The standard library provides utilities for iterating over all values of an enum at compile time.
enum Color {
Red
Green
Blue
}
for color in Color.values() {
c.printf("Color: %d\n", i32(color))
}This is useful for generating lookup tables or ensuring you handle all enum cases.
Enum Methods Proposed
Like structs, enums can have methods associated with them. Methods are defined within the enum block and can operate on the enum instance.
enum Color {
Red
Green
Blue
func toHex(self) string {
return when self {
Color.Red: "#FF0000"
Color.Green: "#00FF00"
Color.Blue: "#0000FF"
}
}
}
var color = Color.Red
var hex = color.toHex() // "#FF0000"String Conversion Proposed
Enums can be converted to their string representation by casting them or using them with string interpolation. This produces the name of the variant as a string. If you need to serialize the value of an enum instead, then you’ll need to cast it to an integer type first.
enum Status {
Idle
Running
Stopped
}
var status = Status.Running
var name = string(status) // "Running"
var value = string(i32(status)) // "1"Annotations Proposed
@flags
The @flags annotation indicates that the enum is intended to be used as a set of bitwise flags. Each variant should be assigned a value that is a power of two (1, 2, 4, 8, etc.) to allow for proper combination using bitwise operations.
@flags enum FilePermission {
Read
Write
Execute
// Combined permissions
ReadWrite = Read | Write
All = Read | Write | Execute
}