Skip to content
Volume N · The TensorCore.si Library

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.

Prologue

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.
Prologue

Set up and run your first program

Install Nim with choosenim or your package manager. Nimble is the package manager.

terminal
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
nim c -r hello.nim
hello.nim
let name = "Ada"
echo "Hello, AI! Welcome to Nim, ", name, "."
Tipnim c -r compiles and runs. Add -d:release for optimised builds.
Chapter 1

Variables and types

let is immutable, var is mutable and const is computed at compile time. $ converts values to strings, and & joins strings.

variables.nim
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 / 1e9
Chapter 2

Control flow

Blocks are defined by indentation. case must cover all values or have an else.

control.nim
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 n
Chapter 3

Procedures

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).

procs.nim
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])
Chapter 4

Sequences, tables and objects

seq is a growable array, Table a hash map and object a record type.

collections.nim
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"
Chapter 5

Templates and macros

Templates paste code at compile time; macros can generate whole sections of code from the syntax tree.

templates.nim
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 s
Part II · AI with Nim

AI 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

ArraymancerTensors and deep learning in Nim.
nimpyCall Python from Nim and Nim from Python.
std/httpclientHTTP requests.
std/jsonJSON parsing and building.

Gradient descent from scratch

Fit y = 3x + 0.5 with sequences and loops.

gradient.nim
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}"
Part III · Build an AI agent

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.

Your goal→Model plans→Tool call→Your code runs the tool→Result back to model→Final answer
agent.nim
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.

Run it locallyInstall Ollama, run 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.
Part IV · Your path

Nim learning roadmap

Tick items off as you go. Your progress is saved in this browser.

Beginner

Intermediate

Advanced

Part IV · Your path

Projects to build

CLI toolA file renamer with command-line options.
Web serverA JSON API with Jester or Prologue.
Tensor demoTrain a small network with Arraymancer.
Reference

Nim cheat sheet

TaskSyntax
Immutablelet x = 5
Mutablevar y = 1.0
Procproc add(a, b: int): int = a + b
Seqvar s = @[1, 2, 3]
To string$x
Format&"{x:.2f}"
Reference

Common mistakes

  • Indentation must use spaces; tabs are not allowed.
  • Identifiers are style-insensitive: myVar and my_var are the same name.
  • Integer types do not mix automatically; convert with .float or int64(x).
Reference

Where to go next

On TensorCore.si: Python volume, C volume.

Keyboard: [ previous language · ] next language · / search the library