Odin Programming Language: Explicit, Performant, dan Joyful Systems Programming

Odin adalah bahasa pemrograman tingkat rendah yang menekankan kesederhanaan, performa, dan "joy of programming". Dengan first-class SIMD support, manual memory management, dan filosofi "explicit over implicit", Odin sangat cocok untuk game development dan sistem programming.

1. Mengapa Odin?

Odin dibuat oleh Ginger Bill (William Schwartz) sebagai alternatif C yang lebih modern dan menyenangkan. Odin digunakan oleh beberapa studio game besar, termasuk developer Polylane dan beberapa proyek game indie.

  • Explicit over Implicit — Tidak ada hidden behavior
  • Manual Memory — Full control dengan allocator interface
  • First-class SIMD — Built-in SIMD vector types
  • No Hidden Allocations — Anda tahu persis di mana memori dialokasikan
  • C Interop — Kompabilitas langsung dengan C libraries
  • Game Ready — Vendor libraries untuk Vulkan, OpenGL, SDL, dll
  • Simple Sintaks — Hanya ~25 keywords
💡 Odin di Game Development

Odin memiliki vendor packages yang sudah include untuk Vulkan, OpenGL, SDL2, DirectX, XInput, dan banyak lagi. Ini menjadikan Odin sangat siap untuk game development dari hari pertama.

2. Instalasi

Bash
# Download dari odin-lang.org
# https://odin-lang.org/docs/install/

# Linux/macOS
git clone https://github.com/odin-lang/Odin.git
cd Odin
make release

# macOS (Homebrew)
brew install odin

# Windows
# Download pre-built binary dari GitHub releases
# Atau build dari source dengan Visual Studio

# Verifikasi
odin version
# odin version dev-2024-xx:xx:xxx

# Jalankan file
odin run hello.odin

# Build
odin build hello.odin

# Build dengan optimasi
odin build hello.odin -o:speed
odin build hello.odin -o:size
odin build hello.odin -o:none  # debug

# Check (type checking tanpa compile)
odin check hello.odin

# Vet (static analysis)
odin vet hello.odin

# Buat proyek baru
mkdir my_project && cd my_project
mkdir src
# Buat src/main.odin
odin run src

3. Dasar-Dasar Odin

Variables dan Types

src/basics.odin
package main

import "core:fmt"
import "core:strings"

main :: proc() {
    // Variables
    nama := "BeebaneLabs"   // inferred type: string
    umur := 25              // inferred type: int
    tinggi := 1.75          // inferred type: f64
    aktif := true           // inferred type: bool

    // Explicit types
    x: int = 42
    y: f64 = 3.14
    s: string = "hello"
    b: bool = true
    c: byte = 'a'
    n: u8 = 255

    // Type sizes: i8, i16, i32, i64, int
    //             u8, u16, u32, u64, uint
    //             f32, f64
    //             rune (Unicode codepoint)

    // Constants
    PI :: 3.14159
    MAX_SIZE :: 100
    GREETING :: "Hello"

    // Typed constants
    MAX_INT :: int(2147483647)

    // Mutable (default immutable)
    mut skor := 100
    skor += 50

    // Strings
    pesan := fmt.tprintf("Nama: %s, Umur: %d", nama, umur)
    fmt.println(pesan)

    fmt.println(strings.to_upper("hello"))
    fmt.println(strings.to_lower("WORLD"))
    fmt.println(strings.contains("hello world", "world"))
    fmt.println(strings.replace("hello world", "world", "Odin", 1))

    // Arrays (fixed size)
    angka := [5]int{1, 2, 3, 4, 5}
    fmt.println(angka[0])

    // Dynamic arrays (slices)
    mut list := make([dynamic]int)
    defer delete(list)
    append(&list, 1)
    append(&list, 2)
    append(&list, 3)
    fmt.println(list)

    // Maps
    mut user := make(map[string]string)
    defer delete(user)
    user["nama"] = "Andi"
    user["email"] = "andi@email.com"
    fmt.println(user["nama"])

    // Structs
    Point :: struct {
        x, y: f64,
    }
    p := Point{3.0, 4.0}
    fmt.println(p.x, p.y)

    // Enums
    Color :: enum {
        Red,
        Green,
        Blue,
    }
    c := Color.Red

    // Bit sets
    Permissions :: bit_set[Permission]
    Permission :: enum {
        Read,
        Write,
        Execute,
    }
    mut perms := Permissions{.Read, .Write}

    // Control flow
    if umur >= 17 {
        fmt.println("Dewasa")
    } else {
        fmt.println("Anak-anak")
    }

    // Switch
    switch c {
    case .Red:
        fmt.println("Merah")
    case .Green:
        fmt.println("Hijau")
    case .Blue:
        fmt.println("Biru")
    }

    // For loops (hanya satu bentuk: for)
    for i in 0..<10 {
        fmt.println(i)
    }

    for item in angka {
        fmt.println(item)
    }

    mut i := 0
    for i < 10 {
        fmt.println(i)
        i += 1
    }

    // For dengan multiple values
    for val, idx in angka {
        fmt.printf("[%d] = %d\n", idx, val)
    }
}

Procedures dan Methods

src/procedures.odin
package main

import "core:fmt"
import "core:math"

// Basic procedure
tambah :: proc(a, b: int) -> int {
    return a + b
}

// Named return value
min_max :: proc(arr: []int) -> (min, max: int) {
    min = arr[0]
    max = arr[0]
    for val in arr {
        if val < min { min = val }
        if val > max { max = val }
    }
    return
}

// Multiple return values
divide :: proc(a, b: f64) -> (result: f64, ok: bool) {
    if b == 0 {
        return 0, false
    }
    return a / b, true
}

// Default parameters (via overloading)
greet :: proc(name: string) -> string {
    return greet(name, "Halo")
}
greet :: proc(name, greeting: string) -> string {
    return greeting + ", " + name + "!"
}

// Polymorphic procedures (generics)
first :: proc(arr: $T/$[]$E) -> E {
    return arr[0]
}

contains :: proc(arr: $T/[]$E, val: E) -> bool {
    for item in arr {
        if item == val {
            return true
        }
    }
    return false
}

// Struct dengan methods
Vector2 :: struct {
    x, y: f64,
}

length :: proc(v: Vector2) -> f64 {
    return math.sqrt(v.x*v.x + v.y*v.y)
}

add :: proc(a, b: Vector2) -> Vector2 {
    return Vector2{a.x + b.x, a.y + b.y}
}

dot :: proc(a, b: Vector2) -> f64 {
    return a.x*b.x + a.y*b.y
}

// Procedures sebagai parameter
apply :: proc(arr: []int, f: proc(int) -> int) {
    for &val in arr {
        val = f(val)
    }
}

// Procedure groups (overloading)
add :: proc(a, b: int) -> int { return a + b }
add :: proc(a, b: f64) -> f64 { return a + b }
add :: proc(a, b: string) -> string { return a + b }

// Using context
custom_allocator :: proc() {
    buf: [1024]byte
    arena := make(allocator.Arena, buf[:])
    context.allocator = arena.allocator()

    // All allocations here use the arena
    data := make([]int, 100)
    _ = data
}

4. Manual Memory Management

Odin memiliki pendekatan yang sangat baik untuk memory management: explicit allocators yang di-pass melalui context.

src/memory.odin
package main

import "core:fmt"
import "core:mem"
import "core:os"

// Default allocator (heap)
default_example :: proc() {
    data := make([]int, 100)
    defer delete(data)
    fmt.println(len(data))
}

// Arena allocator
arena_example :: proc() {
    backing: [1024 * 1024]byte  // 1MB di stack
    arena := make(mem.Arena, backing[:])
    defer destroy(&arena)

    // Semua alokasi di sini pakai arena
    old_alloc := context.allocator
    context.allocator = arena.allocator()
    defer { context.allocator = old_alloc }

    data := make([]int, 1000)  // dialokasikan di arena
    // Tidak perlu delete! Arena akan di-destroy sekaligus
    fmt.println(len(data))
}

// Temp allocator (very fast, scratch memory)
temp_example :: proc() {
    buf := make([]byte, 0, 1024, context.temp_allocator)
    append(&buf, "Hello"...)
    fmt.println(string(buf))
}

// Tracking allocator (debug)
tracking_example :: proc() {
    tracker: mem.Tracking_Allocator
    mem.tracking_allocator_init(&tracker, context.allocator)
    defer mem.tracking_allocator_destroy(&tracker)

    context.allocator = mem.tracking_allocator(&tracker)

    data := make([]int, 100)
    defer delete(data)

    // Print allocation stats
    stats := mem.tracking_allocator_get_stats(&tracker)
    fmt.printf("Allocations: %d\n", stats.allocation_count)
    fmt.printf("Bytes allocated: %d\n", stats.allocated_bytes)
}

// Custom allocator interface
Logging_Allocator :: struct {
    parent: mem.Allocator,
}

logging_alloc :: proc(
    self: ^Logging_Allocator,
    mode: mem.Allocator_Mode,
    size: int,
    old_size: int,
    old_alignment: int,
) -> ([]byte, mem.Allocator_Error) {
    fmt.printf("Alloc: mode=%v, size=%d\n", mode, size)
    return mem.alloc(self.parent, mode, size, old_size, old_alignment)
}

// Context system - semua allocator di-pass melalui context
main :: proc() {
    // Default context
    default_example()

    // Custom allocator
    allocator := mem.JS_Pages  // atau custom
    context.allocator = allocator
}

5. SIMD (Single Instruction Multiple Data)

Odin memiliki first-class SIMD support, memungkinkan operasi paralel pada data yang sangat cepat.

src/simd.odin
package main

import "core:fmt"
import "core:math"
import "core:intrinsics"

// SIMD vector types (built-in)
// f32x4, f32x8, f64x2, f64x4
// i32x4, i32x8, i16x8, i16x16

simd_vector_add :: proc() {
    a := f32x4{1.0, 2.0, 3.0, 4.0}
    b := f32x4{5.0, 6.0, 7.0, 8.0}
    c := a + b  // {6.0, 8.0, 10.0, 12.0}
    fmt.println(c)
}

simd_vector_multiply :: proc() {
    a := f32x4{1.0, 2.0, 3.0, 4.0}
    b := f32x4{2.0, 2.0, 2.0, 2.0}
    c := a * b  // {2.0, 4.0, 6.0, 8.0}
    fmt.println(c)
}

// SIMD dot product
simd_dot :: proc(a, b: f32x4) -> f32 {
    c := a * b
    return c[0] + c[1] + c[2] + c[3]
}

// SIMD di game math
simd_matrix_multiply :: proc() {
    // Vector4 operations
    position := f32x4{1.0, 2.0, 3.0, 1.0}
    scale := f32x4{2.0, 2.0, 2.0, 1.0}
    scaled := position * scale
    fmt.println(scaled)  // {2.0, 4.0, 6.0, 1.0}
}

// Batch processing dengan SIMD
simd_process_array :: proc(input: []f32, output: []f32, scalar: f32) {
    assert(len(input) == len(output))
    assert(len(input) % 4 == 0)

    s := f32x4{scalar, scalar, scalar, scalar}
    for i in 0.. f32 {
    // SSE3 hadd
    tmp := intrinsics.simd_shuffle(v, v, 1, 0, 3, 2) // [1,0,3,2]
    sum1 := v + tmp           // [0+1, 1+0, 2+3, 3+2]
    tmp2 := intrinsics.simd_shuffle(sum1, sum1, 2, 3, 0, 1)
    sum2 := sum1 + tmp2       // semua elemen = total
    return sum2[0]
}

// SIMD comparison
simd_filter :: proc() {
    a := f32x4{1.0, 5.0, 3.0, 8.0}
    threshold := f32x4{4.0, 4.0, 4.0, 4.0}
    mask := a > threshold  // {false, true, false, true}
    fmt.println(mask)
}

6. C Interop

src/c_interop.odin
package main

import "core:fmt"

// Import C library
foreign import lib "system:c"

// C function declarations
foreign lib {
    // Basic C functions
    C.sqrt :: proc(x: f64) -> f64 ---
    C.pow  :: proc(base: f64, exp: f64) -> f64 ---
    C.sin  :: proc(x: f64) -> f64 ---
    C.cos  :: proc(x: f64) -> f64 ---
    C.atan2 :: proc(y: f64, x: f64) -> f64 ---

    // String functions
    C.strlen :: proc(s: cstring) -> uint ---
    C.strcmp :: proc(a, b: cstring) -> int ---

    // Memory
    C.malloc :: proc(size: int) -> rawptr ---
    C.free   :: proc(ptr: rawptr) ---
    C.memset :: proc(dest: rawptr, c: int, n: int) -> rawptr ---
    C.memcpy :: proc(dest, src: rawptr, n: int) -> rawptr ---
}

// Link dengan custom C library
// foreign import my_lib "custom:mylib.a"

// foreign my_lib {
//     C.my_function :: proc(x: int) -> int ---
// }

// Struct yang compatible dengan C
C_Compatible :: struct #align(1) {
    x: f32,
    y: f32,
    z: f32,
}

main :: proc() {
    // Panggil C functions
    fmt.println(C.sqrt(144.0))    // 12.0
    fmt.println(C.pow(2.0, 10.0)) // 1024.0
    fmt.println(C.sin(3.14159 / 2.0)) // ~1.0

    // String interop
    cstr := cstring("Hello from C!")
    fmt.println(C.strlen(cstr))   // 13

    // Memory
    buf := C.malloc(1024)
    C.memset(buf, 0, 1024)
    C.free(buf)
}

// Using vendor C libraries
// import "vendor:SDL2"
// import "vendor:vulkan"
// import "vendor:raylib"

7. Explicit Over Implicit

Filosofi core Odin: semua harus eksplisit, tidak ada hidden behavior yang mengejutkan programmer.

src/explicit.odin
package main

import "core:fmt"
import "core:strings"

// 1. Tidak ada hidden allocation
// Setiap make(), new(), atau slice allocation harus eksplisit
explicit_allocation :: proc(allocator: mem.Allocator) {
    // Anda TAHU ini mengalokasikan memori
    data := make([]int, 100, allocator)
    defer delete(data, allocator)
}

// 2. Tidak ada hidden control flow
// - Tidak ada exception
// - Tidak ada implicit return
// - Tidak ada operator overloading
// - Tidak ada implicit conversion

// 3. Explicit error handling
read_file :: proc(path: string) -> (content: string, ok: bool) {
    data, success := os.read_entire_file(path)
    if !success {
        return "", false
    }
    return string(data), true
}

// 4. Explicit type conversion
explicit_types :: proc() {
    x: int = 42
    y: f64 = f64(x)    // explicit cast
    z: int = int(y)    // explicit cast
    s: string = fmt.tprintf("%d", x)  // explicit conversion
    _ = y; _ = z; _ = s
}

// 5. Explicit bit manipulation
bit_ops :: proc() {
    flags: u8 = 0b1010
    flags |= 0b0001     // set bit
    flags &= ~0b1000    // clear bit
    has_bit := flags & 0b0001 != 0  // check bit
    _ = has_bit
}

// 6. No implicit copies
// Arrays dan structs harus di-copy secara eksplisit
copy_example :: proc() {
    a := [3]int{1, 2, 3}
    b := a  // explicit copy (value semantics)
    b[0] = 99
    fmt.println(a[0])  // still 1
    fmt.println(b[0])  // 99
}

// 7. Explicit distinction value/reference
// Value types (structs, arrays, enums)
// Reference types (maps, dynamic arrays, pointers)

// 8. No hidden defer/RAII
// defer adalah eksplisit
defer_example :: proc() -> int {
    resource := acquire_resource()
    defer release_resource(resource)
    return process(resource)
}

8. Game Development dengan Odin

src/game/main.odin
package game

import "core:fmt"
import "vendor:raylib"

// Game state
Game :: struct {
    player_pos: raylib.Vector2,
    player_speed: f32,
    screen_width: i32,
    screen_height: i32,
}

init_game :: proc() -> Game {
    return Game{
        player_pos = raylib.Vector2{400, 300},
        player_speed = 300.0,
        screen_width = 800,
        screen_height = 600,
    }
}

update :: proc(game: ^Game, dt: f32) {
    // Input handling
    if raylib.IsKeyDown(.W) || raylib.IsKeyDown(.UP) {
        game.player_pos.y -= game.player_speed * dt
    }
    if raylib.IsKeyDown(.S) || raylib.IsKeyDown(.DOWN) {
        game.player_pos.y += game.player_speed * dt
    }
    if raylib.IsKeyDown(.A) || raylib.IsKeyDown(.LEFT) {
        game.player_pos.x -= game.player_speed * dt
    }
    if raylib.IsKeyDown(.D) || raylib.IsKeyDown(.RIGHT) {
        game.player_pos.x += game.player_speed * dt
    }
}

draw :: proc(game: Game) {
    raylib.BeginDrawing()
    defer raylib.EndDrawing()

    raylib.ClearBackground(raylib.RAYWHITE)
    raylib.DrawCircle(i32(game.player_pos.x), i32(game.player_pos.y), 20, raylib.BLUE)
    raylib.DrawText("WASD to move", 10, 10, 20, raylib.DARKGRAY)
    raylib.DrawFPS(10, 40)
}

main :: proc() {
    game := init_game()

    raylib.InitWindow(game.screen_width, game.screen_height, "Odin Game")
    defer raylib.CloseWindow()

    raylib.SetTargetFPS(60)

    for !raylib.WindowShouldClose() {
        dt := raylib.GetFrameTime()
        update(&game, dt)
        draw(game)
    }
}

// Build:
// odin run src/game -o:speed

🧠 Kuis: Odin Programming Language

1. Apa filosofi utama Odin?

  • Explicit over Implicit — semua harus jelas dan tidak tersembunyi
  • Write once, run anywhere
  • Object-oriented first
  • Zero-cost abstractions seperti Rust

2. Bagaimana Odin menangani memory allocation?

  • Garbage collector otomatis
  • Explicit allocators yang di-pass melalui context system
  • Reference counting
  • Hanya stack allocation

3. Mengapa Odin sangat cocok untuk game development?

  • Karena memiliki garbage collector yang cepat
  • Karena mendukung OOP
  • Karena memiliki first-class SIMD, vendor game libraries, dan performa tinggi
  • Karena hanya bisa digunakan untuk game

4. Apa itu SIMD di Odin?

  • Sistem input/output multiplexing
  • Simple Integrated Data Manager
  • Single Instruction Memory Dump
  • Single Instruction Multiple Data — operasi paralel pada vector data

5. Apa perbedaan :: dan := di Odin?

  • Tidak ada perbedaan
  • :: untuk constant/binding, := untuk variable declaration
  • :: untuk mutable, := untuk immutable
  • :: untuk global, := untuk local

📚 Sumber Belajar Lanjutan