Nim
Python-like syntax that compiles to C.
Nim is a compiled language with clean, Python-like indentation syntax. It compiles to C, C++ or JavaScript, producing small, fast binaries, and has a powerful macro system for metaprogramming. It is used for games, systems tools, web backends and scientific computing.
Why learn Nim?
If you like Python's readability but need speed and small binaries, Nim is worth a look.
- Readable: indentation-based syntax similar to Python.
- Fast and small: compiles via C to native code with tiny binaries.
- Metaprogramming: templates and macros remove boilerplate.
Set up and run your first program
Install Nim with choosenim or your package manager. Nimble is the package manager.
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
nim c -r hello.nimlet name = "Ada"
echo "Hello, AI! Welcome to Nim, ", name, "."nim c -r compiles and runs. Add -d:release for optimised builds.Variables and types
let is immutable, var is mutable and const is computed at compile time. $ converts values to strings, and & joins strings.
const layers = 32
let params = 8.03e9
var steps = 0
steps += 1
let model: string = "llama3.1"
echo model & ": " & $layers & " layers"
echo "FP16 GB: ", params * 2 / 1e9Control flow
Blocks are defined by indentation. case must cover all values or have an else.
for epoch in 1..3:
echo "epoch ", epoch
let bits = 8
case bits
of 32: echo "FP32"
of 16: echo "FP16"
of 1..8: echo "low precision"
else: echo "other"
var n = 3
while n > 0:
dec nProcedures
Functions are procs; func marks ones without side effects. The implicit result variable holds the return value. Uniform call syntax lets you write x.f() or f(x).
proc params(layers: int, width = 4096): int64 =
12'i64 * layers * width * width
func square(x: float): float = x * x
proc sum(xs: seq[int]): int =
for x in xs: result += x
echo params(32)
echo 12.0.square
echo sum(@[80, 141, 180])Sequences, tables and objects
seq is a growable array, Table a hash map and object a record type.
import std/[tables, sequtils, strformat]
type Gpu = object
name: string
vram: int
let gpus = @[Gpu(name: "H100", vram: 80), Gpu(name: "H200", vram: 141), Gpu(name: "RTX 5090", vram: 32)]
let big = gpus.filterIt(it.vram >= 80).mapIt(it.name)
echo big
var prices = {"H100": 2.49, "H200": 3.29}.toTable
prices["B200"] = 4.99
for name, p in prices: echo &"{name}: {p} per hour"Templates and macros
Templates paste code at compile time; macros can generate whole sections of code from the syntax tree.
import std/[times, monotimes]
template timeIt(label: string, body: untyped) =
let start = getMonoTime()
body
echo label, " took ", (getMonoTime() - start).inMilliseconds, " ms"
timeIt "sum":
var s = 0
for i in 1..10_000_000: s += i
echo sAI with Nim
Nim's speed and clean syntax suit numerical work. Arraymancer provides tensors and neural networks with CPU and GPU backends, nimpy lets Nim call Python libraries (or be called from Python), and the standard library's HTTP client can call LLM APIs.
The essential libraries
Gradient descent from scratch
Fit y = 3x + 0.5 with sequences and loops.
import std/[sequtils, strformat]
let n = 100
let xs = toSeq(0..<n).mapIt(-1.0 + 2.0 * it.float / (n - 1).float)
let ys = xs.mapIt(3.0 * it + 0.5)
var w, b = 0.0
for step in 1..500:
var gw, gb = 0.0
for i in 0..<n:
let err = w * xs[i] + b - ys[i]
gw += 2 * err * xs[i] / n.float
gb += 2 * err / n.float
w -= 0.1 * gw
b -= 0.1 * gb
echo &"w = {w:.2f}, b = {b:.2f}"Build an AI agent in Nim
std/httpclient and std/json are enough for a tool-calling agent. The %* macro builds JSON from Nim literals.
import std/[httpclient, json]
let client = newHttpClient()
client.headers = newHttpHeaders({"Content-Type": "application/json"})
const url = "http://localhost:11434/v1/chat/completions"
let tools = %*[{"type": "function", "function": {"name": "get_weather", "description": "Weather for a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}]
proc getWeather(city: string): string = "It is 24°C and sunny in " & city & "."
var messages = %*[{"role": "user", "content": "Should I take an umbrella in Lahore today?"}]
for round in 1..5:
let body = %*{"model": "llama3.1", "messages": messages, "tools": tools}
let msg = parseJson(client.postContent(url, $body))["choices"][0]["message"]
messages.add msg
let calls = msg{"tool_calls"}
if calls.isNil or calls.kind != JArray or calls.len == 0:
echo msg["content"].getStr
break
for c in calls:
let args = parseJson(c["function"]["arguments"].getStr)
messages.add %*{"role": "tool", "tool_call_id": c["id"].getStr, "content": getWeather(args["city"].getStr)}msg{"tool_calls"} returns nil instead of raising if the field is missing.
ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.Nim learning roadmap
Tick items off as you go. Your progress is saved in this browser.
Beginner
Intermediate
Advanced
Projects to build
Nim cheat sheet
| Task | Syntax |
|---|---|
| Immutable | let x = 5 |
| Mutable | var y = 1.0 |
| Proc | proc add(a, b: int): int = a + b |
| Seq | var s = @[1, 2, 3] |
| To string | $x |
| Format | &"{x:.2f}" |
Common mistakes
- Indentation must use spaces; tabs are not allowed.
- Identifiers are style-insensitive:
myVarandmy_varare the same name. - Integer types do not mix automatically; convert with
.floatorint64(x).
Where to go next
- Nim tutorial — official
- Nim by Example — examples
- Nimble packages — libraries
On TensorCore.si: Python volume, C volume.
Keyboard: [ previous language · ] next language · / search the library