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.
📋 Daftar Isi
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
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
# 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
# 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.
# 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.
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.
# 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
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
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
# 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"
# 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)