Nim: Fast, Expressive Systems Programming
Nim is a statically typed compiled language that combines the readability of Python with the performance of C. It compiles to C, C++, and JavaScript, giving you the flexibility to target virtually any platform.
In this tutorial, you’ll learn Nim’s Python-like syntax, manual memory management options, powerful metaprogramming with macros, compilation targets, garbage collection, and how to call C libraries directly.
What You’ll Learn
- Nim syntax: Python-like readability with static types
- Compilation model: compile to C, C++, or JavaScript
- Metaprogramming: compile-time macros and templates
- Memory management: GC, RC, or manual
- C interop: calling C libraries without a wrapper
- Package management with Nimble
Why Nim Matters
Nim fills the gap between high-level scripting and low-level systems programming. Its compile-to-C model produces binaries that run as fast as C code. At DodaTech, Nim’s C interop makes it attractive for Durga Antivirus Pro where performance-critical signature scanning meets the need for rapid development.
Learning Path
flowchart LR
A[Systems Programming] --> B[Nim Basics<br/>You are here]
B --> C[Metaprogramming & Macros]
C --> D[C Interop & Embedded]
style B fill:#f90,color:#fff
Nim Syntax
Nim looks and feels like Python but compiles to native code:
# hello.nim
echo "Hello, Nim!"
# Variables
let name = "Alice" # Immutable (like const)
var age = 30 # Mutable
const maxRetries = 3 # Compile-time constant
# Types are inferred but can be explicit
let count: int = 42
let price: float = 19.99
let isActive: bool = true
# Strings
let greeting = "Hello, " & name # Concatenation
echo greeting # Hello, Alice
# Control flow
if age >= 18:
echo "Adult"
elif age >= 13:
echo "Teenager"
else:
echo "Child"
# For loop
for i in 0..<5:
echo i # 0 1 2 3 4Expected output: Hello, Nim!, Hello, Alice, Adult, then 0 1 2 3 4.
Functions and Procedures
# Procedure (returns nothing)
proc greet(name: string) =
echo "Hello, ", name, "!"
# Function (returns a value)
func add(a, b: int): int =
a + b # Implicit return (last expression)
# Function with explicit return
proc multiply(x, y: float): float =
result = x * y # 'result' is the implicit return variable
# Default parameters
proc createWindow(title: string, width = 800, height = 600) =
echo "Creating: ", title, " (", width, "x", height, ")"
# Overloading
proc print(x: int) = echo "Int: ", x
proc print(x: string) = echo "String: ", x
# Usage
greet("Bob") # Hello, Bob!
echo add(10, 20) # 30
createWindow("App") # Creating: App (800x600)
print(42) # Int: 42Expected output: Functions return the last expression. Procedures use result or omit the return type entirely.
Compile to C, C++, or JavaScript
Nim compiles to multiple backends:
nim c hello.nim # Compile to C (default)
nim cpp hello.nim # Compile to C++
nim js hello.nim # Compile to JavaScript
nim c --run hello.nim # Compile and runThe C output is readable C code — not optimized machine code. This makes debugging and interop straightforward:
# Generated C file: nimcache/hello.c
# Compile with any C compiler (gcc, clang, tcc)
gcc -o hello nimcache/hello.cMetaprogramming with Macros
Nim’s macro system operates on the AST at compile time:
import macros
# Template — simple code substitution
template twice(expr: untyped): untyped =
expr
expr
# Usage
twice:
echo "This runs twice"
# Expands to:
# echo "This runs twice"
# echo "This runs twice"
# Macro — operates on AST
macro assertEqual(a, b: untyped): untyped =
result = quote do:
if `a` != `b`:
echo "Assertion failed: ", `a`, " != ", `b`
quit(1)
# Usage
let x = 10
let y = 20
assertEqual(x, y)
# Output: Assertion failed: 10 != 20Expected behavior: The twice template expands inline. The assertEqual macro generates a compile-time check with a descriptive error message.
Memory Management
Nim offers three memory management modes:
| Mode | Flag | Use Case |
|---|---|---|
| GC (refc) | default | General purpose — generational GC |
| ARC | --mm:arc | Deterministic RC, no GC pauses |
| ORC | --mm:orc | Cycle-collecting ARC |
| None | --gc:none | Embedded, real-time, no runtime |
# ARC mode — deterministic, no GC pauses
# Compile: nim c --mm:arc myapp.nim
type
Person = ref object
name: string
age: int
proc newPerson(name: string, age: int): Person =
Person(name: name, age: age)
proc use() =
let p = newPerson("Alice", 30)
echo p.name # No GC pause possible
# p is freed here (ARC deletes when out of scope)C Interop
Calling C libraries directly — no FFI bindings needed:
# Call C standard library directly
proc printf(format: cstring): cint {.importc, varargs.}
discard printf("Hello from C: %d %s\n", 42, "Nim")
# Link with a C library
{.passL: "-lsqlite3".}
proc sqlite3_open(path: cstring, db: pointer): cint {.importc.}
# Export Nim functions to C
proc myFunction(x: cint): cint {.exportc.} =
x * 2Expected behavior: printf from libc is called directly. The Nim function myFunction is callable from C code after compilation.
Common Mistakes
1. Confusing let, var, and const
let is immutable (cannot rebind), var is mutable, const is computed at compile time. Use let by default.
2. Forgetting That Strings Are Sequences
Nim strings are mutable. s.add('!') modifies in place. Use & for concatenation.
3. Ignoring the GC
The default GC can cause unpredictable pauses. For game development, use --mm:arc or --gc:none.
4. Not Using Nimble for Dependencies
Nimble is Nim’s package manager. Use nimble install packagename instead of manual vendoring.
5. Overusing Macros
Macros execute at compile time and can make code hard to debug. Use templates and generics before reaching for macros.
6. Forgetting Case Sensitivity
Nim is case-sensitive. myVar, myvar, and MYVAR are different identifiers.
Practice Questions
1. What does Nim compile to?
C, C++, or JavaScript. The C backend produces portable C code that any C compiler can build.
2. What is a template in Nim?
A compile-time code substitution that works on the AST level. Templates are simpler than macros and suitable for most code generation needs.
3. How do you call a C function from Nim?
Use the importc pragma: proc printf(fmt: cstring): cint {.importc, varargs.}
4. What memory management modes does Nim support?
GC (refc), ARC (deterministic RC), ORC (cycle-collecting ARC), and none (manual).
5. Challenge: Write a macro that logs expression values.
Create a debug macro: debug(x + y) prints "x + y = 42" and returns 42.
Mini Project: Simple Web Server
import asynchttpserver, asyncdispatch
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =
await req.respond(Http200, "Hello, Nim!")
waitFor server.serve(Port(8080), cb)
echo "Server running on http://localhost:8080"FAQ
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro