Nim Systems Programming: Metaprogramming, Memory Management, dan Performance

Nim adalah bahasa sistem yang menggabungkan performa C dengan sintaks yang indah dan produktif. Dengan metaprogramming yang powerful, memory management yang fleksibel, dan kemampuan compile ke C/C++/JavaScript, Nim sangat versatile.

1. Mengapa Nim?

Nim dibuat oleh Andreas Rumpf dan pertama kali dirilis pada 2008. Bahasa ini dirancang untuk menjadi "bahasa sistem yang produktif" — memberikan kontrol tingkat rendah seperti C sambil menawarkan sintaks yang bersih dan fitur tingkat tinggi.

  • Python-like Sindentation-based syntax — Sintaks berbasis indentasi, sangat bersih
  • Compiles to C — Binary sangat kecil dan cepat
  • Flexible Memory Management — GC, ARC, atau manual
  • Powerful Metaprogramming — Macros dan templates
  • Cross-compilation — Compile ke target platform apapun
  • JavaScript backend — Bisa compile ke JS untuk web
💡 Unique Selling Point Nim

Nim compile ke C, bukan langsung ke machine code. Ini artinya Nim bisa memanfaatkan semua optimizer C (GCC, Clang, MSVC) dan bisa cross-compile ke platform apapun yang memiliki C compiler.

Instalasi dan Setup

Bash
# Menggunakan choosenim (recommended)
curl https://nim-lang.org/choosenim/init.sh -sSf | sh

# macOS
brew install nim

# Windows (download dari nim-lang.org)
# Atau menggunakan choosenim
# https://nim-lang.org/install_windows.html

# Verifikasi
nim --version
# Nim Compiler Version 2.x.x

# Install Nimble (package manager)
# Sudah termasuk dengan Nim

# Buat proyek baru
nimble init my_project
cd my_project

# Jalankan
nim c -r src/my_project.nim

# Compile dengan optimasi
nim c -d:release src/my_project.nim

# Compile ke JavaScript
nim js -o:output.js src/my_project.nim

# Jalankan test
nimble test

3. Dasar-Dasar Nim

Variables, Types, dan Control Flow

src/basics.nim
# Variables
var nama = "BeebaneLabs"    # mutable
let umur = 25               # immutable (runtime)
const pi = 3.14159          # compile-time constant

# Type annotations
var x: int = 42
var y: float = 3.14
var s: string = "hello"
var b: bool = true
var c: char = 'a'

# Basic types
# int, int8, int16, int32, int64
# uint, uint8, uint16, uint32, uint64
# float, float32, float64
# string, char, bool

# String operations
echo "Nama: ", nama, ", Umur: ", umur
echo "Panjang: ", nama.len
echo "Uppercase: ", nama.toUpper()
echo "Contains 'Bee': ", nama.contains("Bee")
echo &"Nama: {nama}, Umur: {umur}"  # string interpolation

# Arrays
var angka = [1, 2, 3, 4, 5]           # fixed-size array
var dyn_arr = @[1, 2, 3, 4, 5]        # dynamic array (seq)
dyn_arr.add(6)
dyn_arr.insert(0, 99)
echo dyn_arr.len
echo dyn_arr[0]

# If-elif-else
let nilai = 85
if nilai >= 90:
  echo "A"
elif nilai >= 80:
  echo "B"
elif nilai >= 70:
  echo "C"
else:
  echo "D"

# Case statement
case nilai
of 90..100: echo "A"
of 80..89: echo "B"
of 70..79: echo "C"
else: echo "D"

# For loops
for i in 0..9:
  echo i

for i in countdown(10, 0):
  echo i

for item in ["apel", "jeruk", "mangga"]:
  echo item

# While
var i = 0
while i < 10:
  echo i
  inc i

# Functions
proc tambah(a, b: int): int =
  return a + b

# Short syntax
proc kali(a, b: int): int = a * b

# No return type
proc greet(nama: string) =
  echo "Halo, ", nama, "!"

# Default parameters
proc buatUser(nama: string, umur: int = 18) =
  echo &"User: {nama}, Umur: {umur}"

# Named arguments
buatUser(umur=25, nama="Andi")

# Operators can be defined
proc `+`(a, b: string): string =
  a & b

echo "Hello" & " " & "World"

# Procedures with multiple return values
proc minMax(arr: seq[int]): (int, int) =
  var min_val = arr[0]
  var max_val = arr[0]
  for val in arr:
    if val < min_val: min_val = val
    if val > max_val: max_val = val
  return (min_val, max_val)

let (mn, mx) = minMax(@[3, 1, 4, 1, 5, 9])
echo &"Min: {mn}, Max: {mx}"

4. Memory Management

Nim mendukung berbagai strategi memory management, dari garbage collection sampai manual control penuh.

src/memory.nim
# Nim mendukung beberapa memory management modes:
# 1. --mm:orc (default di Nim 2.x) - Ownership + RC
# 2. --mm:arc - Automatic Reference Counting
# 3. --mm:refc - Traditional GC (reference counting)
# 4. --mm:markAndSweep - Mark & Sweep GC
# 5. --mm:boehm - Boehm GC
# 6. --mm:none - Manual (no GC)

# Compile dengan mode tertentu:
# nim c --mm:orc src/app.nim
# nim c --mm:arc src/app.nim
# nim c --mm:none src/app.nim

# Stack allocation (default untuk value types)
type
  Point = object
    x, y: float64

var p = Point(x: 1.0, y: 2.0)  # stack allocated

# Heap allocation
var s = newSeq[int](1000)         # heap allocated seq
var str = newString(100)          # heap allocated string

# Manual memory dengan alloc/dealloc (mm:none)
proc manualMemory() =
  let buffer = cast[ptr UncheckedArray[int]](alloc(100 * sizeof(int)))
  buffer[0] = 42
  buffer[1] = 84
  echo buffer[0], " ", buffer[1]
  dealloc(buffer)

# ORC (Ownership + Reference Counting)
# Default di Nim 2.x, sangat efisien
type
  Node = ref object
    data: int
    next: Node

proc newNode(data: int): Node =
  Node(data: data, next: nil)

var head = newNode(1)
head.next = newNode(2)
head.next.next = newNode(3)

# Destructor dengan 'destroy'
type
  MyResource = object
    id: int

proc `=destroy`(r: MyResource) =
  echo "Destroying resource ", r.id

proc `=copy`(dst: var MyResource, src: MyResource) =
  echo "Copying resource"
  dst.id = src.id

# Move semantics
proc takeOwnership(s: sink seq[int]) =
  echo "Saya memiliki seq ini: ", s.len

var data = @[1, 2, 3, 4, 5]
takeOwnership(data)
# data tidak valid lagi setelah move

# View types (borrow)
proc processView(data: openArray[int]) =
  for item in data:
    echo item

5. Metaprogramming

Nim memiliki salah satu sistem metaprogramming terbaik dari semua bahasa pemrograman. Compile-time function execution (CTFE), templates, dan macros sangat powerful.

src/macro_sample.nim
import macros, strutils

# Compile-time function execution (CTFE)
proc fibonacci(n: int): int {.compileTime.} =
  if n <= 1: return n
  return fibonacci(n - 1) + fibonacci(n - 2)

const fib20 = fibonacci(20)  # Dihitung saat compile!

# Templates - code substitution
template measure(body: untyped) =
  let t0 = cpuTime()
  body
  let elapsed = cpuTime() - t0
  echo "Elapsed: ", elapsed, " seconds"

measure:
  var total = 0
  for i in 0..1_000_000:
    total += i
  echo "Total: ", total

# Template dengan generics
template withFile(f: File, mode: FileMode, body: untyped) =
  var f: File
  if open(f, mode):
    try:
      body
    finally:
      close(f)
  else:
    echo "Gagal membuka file"

# Macro - AST manipulation
macro repeat(n: static[int], body: untyped): untyped =
  result = newStmtList()
  for i in 0.. 0:
    echo "Positif: ", n

6. Foreign Function Interface (FFI)

Nim bisa memanggil C library secara langsung tanpa FFI wrapper.

src/ffi.nim
# Import C library
{.passL: "-lm".}  # Link dengan libm
proc c_sqrt(x: cdouble): cdouble {.importc: "sqrt", header: "".}
proc c_pow(base, exp: cdouble): cdouble {.importc: "pow", header: "".}
proc c_sin(x: cdouble): cdouble {.importc: "sin", header: "".}

echo c_sqrt(144.0)     # 12.0
echo c_pow(2.0, 10.0)  # 1024.0
echo c_sin(3.14159)     # ~0

# Import C header file
{.passL: "-lcurl".}
type
  CurlHandle = pointer

proc curl_easy_init(): CurlHandle {.importc, header: "".}
proc curl_easy_cleanup(handle: CurlHandle) {.importc, header: "".}

# Import entire C header
{.push header: "".}
proc c_memset(s: pointer, c: cint, n: csize_t) {.importc: "memset".}
proc c_memcpy(dest, src: pointer, n: csize_t) {.importc: "memcpy".}
proc c_strlen(s: cstring): csize_t {.importc: "strlen".}
{.pop.}

# Wrapping C library dengan wrapper module
# mylib.nim:
# {.compile: "mylib.c".}
# proc my_function(x: int): int {.importc.}

# C++ interop
{.push header: "", nodecl.}
proc echo_cpp(s: cstring) {.importcpp: "std::cout << # << std::endl".}
{.pop.}

# Struct interop
type
  Timeval {.importc: "struct timeval", header: "".} = object
    tv_sec: clong
    tv_usec: clong

# Inline C code
proc getTimestamp(): int64 {.inline.} =
  var tv: Timeval
  {.emit: """
  gettimeofday(&`tv`, NULL);
  """.}
  return tv.tv_sec * 1000000 + tv.tv_usec

7. Async dan Concurrency

src/async_sample.nim
import std/[asyncdispatch, asynchttpserver, asyncnet, os]

# Async procedures
proc fetchData(url: string): Future[string] {.async.} =
  echo "Fetching: ", url
  await sleepAsync(1000)  # Simulate network delay
  return "Data from " & url

proc processData() {.async.} =
  # Await mengambil result dari Future
  let data1 = await fetchData("https://api.example.com/users")
  let data2 = await fetchData("https://api.example.com/posts")
  echo data1
  echo data2

# Parallel execution
proc parallelFetch() {.async.} =
  let f1 = fetchData("https://api1.example.com")
  let f2 = fetchData("https://api2.example.com")
  let f3 = fetchData("https://api3.example.com")

  # Jalankan semua secara paralel
  let results = await all(f1, f2, f3)
  for r in results:
    echo r

# HTTP Server
proc handleRequest(req: Request) {.async.} =
  case req.url.path
  of "/":
    await req.respond(Http200, "Hello from Nim!")
  of "/api/users":
    await req.respond(Http200, """[{"id": 1, "name": "Andi"}]""",
      newHttpHeaders([("Content-Type", "application/json")]))
  else:
    await req.respond(Http404, "Not Found")

proc main() {.async.} =
  var server = newAsyncHttpServer()
  echo "Server berjalan di http://localhost:8080"
  server.listen(Port(8080))

  while true:
    if server.shouldAcceptRequest():
      await server.acceptRequest(handleRequest)
    else:
      await sleepAsync(100)

waitFor main()

# Threads
import std/threadpool

proc heavyComputation(n: int): int =
  result = 0
  for i in 0..
                

8. Objects dan OOP

src/oop.nim
type
  # Base object
  Animal = object of RootObj
    name: string
    age: int

  # Inheritance
  Dog = object of Animal
    breed: string

  Cat = object of Animal
    indoor: bool

# Methods
proc speak(a: Animal): string =
  "..."
proc speak(d: Dog): string =
  "Woof!"
proc speak(c: Cat): string =
  "Meow!"

# Constructor-like procs
proc newDog(name: string, age: int, breed: string): Dog =
  Dog(name: name, age: age, breed: breed)

proc newCat(name: string, age: int, indoor: bool = true): Cat =
  Cat(name: name, age: age, indoor: indoor)

# Ref types (heap allocated, polymorphism)
type
  Shape = ref object of RootObj
  Circle = ref object of Shape
    radius: float
  Rectangle = ref object of Shape
    width, height: float

method area(s: Shape): float {.base.} =
  raise newException(CatchableError, "Not implemented")

method area(c: Circle): float =
  PI * c.radius * c.radius

method area(r: Rectangle): float =
  r.width * r.height

# Polymorphism
let shapes: seq[Shape] = @[
  Circle(radius: 5.0),
  Rectangle(width: 3.0, height: 4.0),
  Circle(radius: 2.0),
]

for shape in shapes:
  echo "Area: ", shape.area()

# Case objects (tagged unions)
type
  JsonNodeKind = enum
    JNull, JBool, JInt, JFloat, JString, JArray, JObject

  JsonNode = object
    case kind: JsonNodeKind
    of JNull: discard
    of JBool: boolVal: bool
    of JInt: intVal: int64
    of JFloat: floatVal: float64
    of JString: strVal: string
    of JArray: elems: seq[JsonNode]
    of JObject: fields: seq[(string, JsonNode)]

# Converter
proc toJson(n: JsonNode): string =
  case n.kind
  of JNull: "null"
  of JBool: $n.boolVal
  of JInt: $n.intVal
  of JFloat: $n.floatVal
  of JString: "\"" & n.strVal & "\""
  of JArray:
    var parts: seq[string]
    for e in n.elems: parts.add(toJson(e))
    "[" & parts.join(", ") & "]"
  of JObject:
    var parts: seq[string]
    for (k, v) in n.fields: parts.add("\"" & k & "\": " & toJson(v))
    "{" & parts.join(", ") & "}"

# Interfaces via concepts
type
  Printable = concept x
    echo x

proc printIt[T: Printable](item: T) =
  echo item

9. Nimble Packages

nimble.toml
# Package info
[Package]
name = "my_project"
author = "Andi"
version = "0.1.0"
description = "My awesome Nim project"
license = "MIT"
srcDir = "src"
binDir = "bin"
bin = "my_project"

[Deps]
requires "nim >= 2.0.0"
requires "jester >= 0.5.0"
requires "chronicles >= 0.10.0"
requires "jsony >= 1.1.0"
requires "norm >= 2.7.0"
Bash
# Install package
nimble install jester
nimble install chronicles

# Search packages
nimble search web

# Popular packages:
# - jester (web framework, seperti Sinatra)
# - prologue (full-stack web framework)
# - karax (frontend framework)
# - chronicles (logging)
# - jsony (fast JSON)
# - norm (ORM)
# - puppy (HTTP client)
# - cligen (CLI argument parser)
# - unittest2 (testing)
# - neo (linear algebra)

🧠 Kuis: Nim Systems Programming

1. Mengapa Nim compile ke C dan bukan langsung ke machine code?

  • Karena Nim tidak support machine code
  • Untuk memanfaatkan C compiler optimizer dan cross-compilation
  • Karena C lebih cepat dari machine code
  • Karena Nim hanya bisa berjalan di atas C runtime

2. Apa perbedaan `var`, `let`, dan `const` di Nim?

  • Ketiganya sama saja
  • var untuk angka, let untuk string, const untuk boolean
  • var=mutable, let=immutable(runtime), const=compile-time
  • var=global, let=local, const=constant

3. Apa itu template di Nim?

  • Code substitution yang terjadi saat compile time
  • HTML template untuk web
  • Function yang berjalan di runtime
  • Type definition

4. Memory management mode apa yang default di Nim 2.x?

  • Boehm GC
  • ORC (Ownership + Reference Counting)
  • Manual (no GC)
  • Mark and Sweep

5. Apa keunggulan `sink` parameter di Nim?

  • Membuat parameter menjadi mutable
  • Mengcopy data secara deep
  • Move semantics - ownership dipindahkan, tidak ada copy
  • Membuat parameter menjadi async

📚 Sumber Belajar Lanjutan