Enums, Unions and Switch

Table of Contents

1. Enumeration

Enumerations can be defined with enum keyword

  Computer_Type :: enum {
      Laptop, // value 0
      Desktop, // value 1
      Mainframe, // value 2
  }

Similar to other languages, each enumeration is associated with an integer value. When enum instances are initialized with zero value, by default, it’s set to the first item. Moreover, to initialize with specific enumeration item, we can use the simplified dot notation.

  ct := .Mainframe
  // which is equivalent to
  ct := Computer_Type.Mainframe

1.1. Explicit Enumeration Numbering

We can explicit map each enumeration item to an integer manually. By default, it increases by 1.

  Computer_Type :: enum {
    Laptop = 1, // value 1
    Desktop,    // value 2
    Mainframe,  // value 3
  }

  Computer_Type :: enum {
    Laptop = 0,    // value 0 
    Desktop,       // value 1
    Mainframe = 5, // value 5
    Broken,        // value 6
    Gamer_Rig,     // value 7
  }

1.2. Backing Type

By default, enum uses int as backing type, which means that behind the scenes, an enum uses an int to store the current value. We can change the backing type by specifying the type after enum keyword

  Animal_Type :: enum u8 {
      Cat,
      Rabbit,
  }

2. Unions

Unions can store both type information and value. We should specify the type names inside a union. When initializing a union, we should always remember to specify the data type.

  MyUnion :: union {
      f32,
      int,
      PersonData,
  }

  PersonData :: struct {
      health: int,
      age: int,
  }

  val: MyUnion = int(12)

2.1. Grammar Sugar for Checking If Union is Holding a Specific Variant

Soemtimes, we do not need the entire switch statement to get a value. We can just use variable.(variant) to try unpacking the union.

  f32val, f32val_ok := val.(f32)

  if f32val_ok {
      // f32val is ok to use here
  }

  // or equivalently, initialize within if-statement
  if f32val, ok := val.(f32); ok {
      
  }

  // If we want to modify the value
  if f32val, ok := &val.(f32); ok {
      f32val^ = 7
  }

Note, the zero value of a union is nil. If a union is marked non-nil, then the zero value will be the zero value of the first variant.

  Shape :: union #no_nil {
      Shape_Circle,
      Shape_Square,
  }

  Shape_Circle :: struct {
      radius: f32,
  }
  Shape_Square :: struct {
      width: f32,
  }

  shape: Shape // zero value: Variant Shape_Circle

2.2. Tag of Union

Without non-nil, there are 3 tags for Shape union: 0 for nil, 1 for ShapeCircle, 2 for ShapeSquare. With non-nil, there are 2 tags: 0 for ShapeCircle, 1 for ShapeSquare.


3. Switch

3.1. Switch on Enums

In Odin, we can use switch case grammar to match variables. Unlike C/C++, in Odin, if one branch is matched, it will automatically break. If we want to fall through to next case, we should explicitly use fallthrough keyword.

  switch ct {
    case .Laptop:
      fmt.println("Laptop")
    case .Desktop:
      fmt.println("Desktop")
    case .Mainframe:
      fmt.println("Mainframe")
  }

By default, switch requires you to exhaust all cases. But we can declare #partial before switch to tell Odin non-exhaustive matching.

  #partial switch ct {
      case .Laptop:
        fmt.println("Laptop")
      case .Desktop:
        fmt.println("Desktop")
  }

3.2. Switch on Unions

We can pattern matching on union variants by type names.

  switch v in val {
      case int:
        // we can use v as an int here
        fmt.println("int: ")
      case f32:
        // we can use v as an f32 here
      case PersonData:
        // we can use v as of type PersonData, like accessing fields
        fmt.println(v.age)
  }

To make v modifiable inside switch case, we should use references: switch &v in val

Date: 2026-07-17 Fri