Intermediate

Functions in Nim

Learn how to define and use functions in Nim - procedures, parameters, default values, the result variable, recursion, and higher-order procs with Docker-ready examples

Functions are the primary unit of code reuse in Nim. They package logic behind a name, accept inputs as parameters, and hand back results - letting you build programs from small, testable pieces rather than one long script.

Nim is genuinely multi-paradigm, and its function model reflects that. The same proc keyword that defines a plain procedure can also be passed around as a first-class value, returned from another proc, or captured in a closure. Nim adds a dedicated func keyword to mark side-effect-free functions, and an implicit result variable that makes returning values feel effortless. Thanks to static typing, every parameter and return type is checked at compile time, so a mismatched argument is a build error, not a runtime surprise.

In this tutorial you’ll define procedures with typed parameters and return values, use default arguments and named calls, modify caller variables with var parameters, write recursive procedures, and pass procedures to other procedures as higher-order functions. Each example is self-contained and runnable through Docker.

Defining Procedures

The core building block is proc. A procedure lists its parameters with their types, optionally declares a return type after :, and produces a value either through the implicit result variable or by leaving an expression as the last line of its body.

Create a file named functions_basics.nim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# A procedure using the implicit 'result' variable
proc add(a: int, b: int): int =
  result = a + b

# Parameters of the same type can be grouped; the last
# expression in the body becomes the return value
proc multiply(a, b: int): int =
  a * b

# A procedure with no return type performs an action only
proc greet(name: string) =
  echo "Hello, ", name, "!"

echo "add(3, 4) = ", add(3, 4)
echo "multiply(5, 6) = ", multiply(5, 6)
greet("Nim")

# Uniform Function Call Syntax: a.add(b) is the same as add(a, b)
echo "10.add(5) = ", 10.add(5)

Notice the last line: Nim’s Uniform Function Call Syntax (UFCS) lets you write 10.add(5) instead of add(10, 5). The first argument simply moves in front of the dot, which is why method-style calls like greeter.greet() work without defining methods on a class.

Default Parameters and Named Arguments

Parameters can declare default values, making them optional at the call site. Callers may also pass arguments by name in any order, which keeps calls readable when a procedure has several parameters.

Create a file named functions_defaults.nim:

1
2
3
4
5
6
7
8
9
# 'exponent' defaults to 2 when the caller omits it
proc power(base: int, exponent: int = 2): int =
  result = 1
  for i in 1 .. exponent:
    result *= base

echo "power(5) = ", power(5)                          # uses default exponent
echo "power(2, 8) = ", power(2, 8)                    # positional arguments
echo "power(base = 3, exponent = 3) = ", power(base = 3, exponent = 3)

The result variable is automatically declared and initialized (to 1 here only because we assign it; numeric defaults are otherwise 0). You mutate it through the body and it is returned when the procedure ends - no explicit return statement needed.

Var Parameters and Pure Functions

By default, parameters are immutable inside a procedure. To let a procedure modify the caller’s variable, mark the parameter var (pass-by-reference). When a procedure has no side effects at all, declare it with func - the compiler then rejects any hidden side effect, documenting and enforcing purity.

Create a file named functions_var.nim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# A 'var' parameter is modified in place in the caller
proc double(x: var int) =
  x = x * 2

var n = 21
double(n)
echo "After double: ", n

# 'func' is shorthand for 'proc' with no side effects
func square(x: int): int =
  x * x

echo "square(9) = ", square(9)

A func cannot call echo, mutate global state, or perform I/O - those are side effects. This makes func ideal for pure calculations and lets the compiler reason more aggressively about your code.

Recursion

A procedure may call itself. Recursion expresses naturally repetitive, self-similar problems - like computing a factorial or a Fibonacci number - without an explicit loop. Each call works on a smaller input until it reaches a base case.

Create a file named recursion.nim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Factorial: n! = n * (n-1)!
proc factorial(n: int): int =
  if n <= 1:
    1
  else:
    n * factorial(n - 1)

# Fibonacci: each number is the sum of the previous two
proc fib(n: int): int =
  if n < 2:
    n
  else:
    fib(n - 1) + fib(n - 2)

echo "factorial(5) = ", factorial(5)
echo "fib(10) = ", fib(10)

Here the if/else is an expression: whichever branch runs supplies the procedure’s return value. This is why neither branch needs result = or return.

Higher-Order Functions and Closures

Because procedures are first-class values in Nim, you can pass one procedure to another, store procedures in variables, and return new procedures. A procedure that captures variables from its surrounding scope is a closure - the foundation of Nim’s functional style.

Create a file named higher_order.nim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Takes a procedure 'f' as a parameter and applies it twice
proc applyTwice(f: proc(x: int): int, value: int): int =
  f(f(value))

# A named procedure can be passed as an argument
proc increment(x: int): int = x + 1

echo "applyTwice(increment, 10) = ", applyTwice(increment, 10)

# An anonymous procedure (lambda) passed inline
echo "applyTwice(square, 5) = ", applyTwice(proc(x: int): int = x * x, 5)

# A closure: the returned proc captures 'amount' from its scope
proc makeAdder(amount: int): proc(x: int): int =
  proc(x: int): int = x + amount

let add10 = makeAdder(10)
echo "add10(7) = ", add10(7)

makeAdder(10) returns a brand-new procedure that remembers amount = 10. Calling add10(7) then yields 17. This pattern - functions that build other functions - is what makes higher-order programming so expressive.

Running with Docker

You don’t need Nim installed locally. Pull the official image once, then compile and run each example with nim c -r (compile to C, then run).

1
2
3
4
5
6
7
8
9
# Pull the official Nim image
docker pull nimlang/nim:alpine

# Run each example (compile and execute)
docker run --rm -v $(pwd):/app -w /app nimlang/nim:alpine nim c -r functions_basics.nim
docker run --rm -v $(pwd):/app -w /app nimlang/nim:alpine nim c -r functions_defaults.nim
docker run --rm -v $(pwd):/app -w /app nimlang/nim:alpine nim c -r functions_var.nim
docker run --rm -v $(pwd):/app -w /app nimlang/nim:alpine nim c -r recursion.nim
docker run --rm -v $(pwd):/app -w /app nimlang/nim:alpine nim c -r higher_order.nim

Expected Output

functions_basics.nim:

add(3, 4) = 7
multiply(5, 6) = 30
Hello, Nim!
10.add(5) = 15

functions_defaults.nim:

power(5) = 25
power(2, 8) = 256
power(base = 3, exponent = 3) = 27

functions_var.nim:

After double: 42
square(9) = 81

recursion.nim:

factorial(5) = 120
fib(10) = 55

higher_order.nim:

applyTwice(increment, 10) = 12
applyTwice(square, 5) = 625
add10(7) = 17

Key Concepts

  • proc is the workhorse - it defines procedures that take typed parameters and optionally return a typed value; func is the same thing with a compiler-enforced no-side-effects guarantee.
  • The implicit result variable is pre-declared in every procedure with a return type; assign to it and it is returned automatically, with no return statement required.
  • Last expression returns - if the final line of a proc body is an expression matching the return type, its value is returned, so if/else and single-line bodies work without result.
  • Default and named arguments make procedures flexible: parameters can supply default values, and callers can pass arguments by name in any order.
  • var parameters enable pass-by-reference so a procedure can modify the caller’s variable in place; ordinary parameters are immutable inside the body.
  • Uniform Function Call Syntax (UFCS) means obj.proc(args) and proc(obj, args) are interchangeable, giving Nim its clean method-style calls without true methods.
  • Procedures are first-class values - they can be passed as arguments, returned from other procs, and captured in closures, enabling higher-order functional programming.

Running Today

All examples can be run using Docker:

docker pull nimlang/nim:alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining