Types

💡 Best Practices Summary

C Interop Rule of Thumb

  • Use b32 instead of bool when creating structs sent to Vulkan or C libraries.

  • Use bit_set instead of raw u32 bitmasks for cleaner engine flags.

  • Use #align(16) when matching Vulkan Uniform Buffer Objects (UBOs).


Types in Odin

Odin features a strong, explicit, data-oriented type system. There are no implicit type conversions, giving you total layout control—essential for C ABI bindings, memory packing, and Vulkan GPU data structures.


🔢 1. Basic & Interop Types

Odin uses explicit bit-width types. Avoid int when defining hardware/Vulkan structures—use sized types instead.

// Integers (Signed & Unsigned)
i8, i16, i32, i64, i128
u8, u16, u32, u64, u128

// Floats & Booleans
f32, f64
bool // 1 byte
b32  // 4 bytes (Standard for C ABI / Vulkan bools)

// Raw Pointers
rawptr // Equivalent to void* in C

🧱 2. Structs & Layout Control

Structs are ordered collections of variables. By default, Odin orders fields in memory sequence, but allows explicit alignment and packing for GPU compatibility.

// Standard Struct
Vector3 :: struct {
    x: f32,
    y: f32,
    z: f32,
}

// Memory-Packed Struct (No padding between fields - good for network packets)
PackedData :: struct #packed {
    id:   u8,  // 1 byte
    val:  u32, // 4 bytes (normally padded to offset 4, now at offset 1)
}

// Explicit Alignment (Crucial for Vulkan Uniform Buffers / std140 / std430 alignment)
GPUUniforms :: struct #align(16) {
    model_matrix: [16]f32,
    color:        [4]f32,
}

🏷️ 3. Distinct Types (Type Safety Hacks)

A distinct type creates a brand new type with the same memory layout as the underlying type, but the compiler forbids mixing them without an explicit cast.

Game Engine Utility Use distinct to prevent accidentally passing an EntityID into a function expecting a TextureID, even if both are internally u32.
EntityID  :: distinct u32
TextureID :: distinct u32

player_id: EntityID = 10
tex_id: TextureID = 10

// ERROR at compile-time: Cannot mix distinct types!
// player_id = tex_id 

// Must be explicitly cast:
player_id = EntityID(tex_id)

🏷️ 4. Unions & Tagged Unions (Type-Safe Polymorphism)

Odin unions are tagged unions by default. They store a value and automatically keep track of which type is currently stored inside.

ShapeCircle :: struct { radius: f32 }
ShapeBox    :: struct { width, height: f32 }

// Tagged Union
Shape :: union {
    ShapeCircle,
    ShapeBox,
}

draw_shape :: proc(shape: Shape) {
    // Type switch on the tagged union
    switch s in shape {
    case ShapeCircle:
        // 's' is automatically typed as ShapeCircle here!
        fmt.println("Circle radius:", s.radius)
    case ShapeBox:
        fmt.println("Box dimensions:", s.width, s.height)
    case:
        fmt.println("Empty shape")
    }
}

🎛️ 5. Enums & Bit Sets

Enums

Enums represent a discrete list of named values. You can specify the underlying integer storage type.

RenderMode :: enum u32 {
    Forward,
    Deferred,
    Raytraced,
}

Bit Sets (Cleaner Flag Masks)

Instead of manually doing bitwise ORs (FLAG_A | FLAG_B) like in C, Odin uses bit_set over an enum.

VulkanFeatureFlag :: enum {
    ValidationLayers,
    RayTracing,
    MeshShaders,
}

// Creates a type-safe bitfield
VulkanFeatures :: bit_set[VulkanFeatureFlag; u32]

// Usage:
features: VulkanFeatures = {.ValidationLayers, .MeshShaders}

// Check flags cleanly:
if .RayTracing in features {
    // Raytracing is enabled
}