Beginner

Variables and Types in Nim

Learn about variables, data types, and type conversions in Nim with practical Docker-ready examples

Nim’s type system is one of its defining features — statically typed and strongly checked at compile time, yet remarkably ergonomic thanks to powerful type inference. Where languages like Python give you freedom at the cost of runtime surprises, and languages like Java demand verbose declarations, Nim strikes a balance: the compiler catches type errors before your code ever runs, but you rarely need to spell out types explicitly.

Understanding Nim’s variable system starts with three keywords: var, let, and const. Each gives you a different level of mutability, and choosing the right one signals intent to both the compiler and anyone reading your code. Combined with Nim’s rich set of built-in types, distinct types for domain safety, and seamless type conversions, you’ll find a type system that’s both practical and expressive.

In this tutorial, you’ll learn how to declare variables with different mutability levels, work with Nim’s primitive and compound types, convert between types safely, and use distinct types to prevent logical errors at compile time.

Variable Declarations: var, let, and const

Nim provides three ways to bind a name to a value, each with different guarantees about mutability.

Create a file named variables.nim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# === var: mutable variables ===
var count: int = 0
count = 10
var name = "Nim"  # Type inferred as string
name = "Nim Language"

echo "count = ", count
echo "name = ", name

# === let: immutable bindings (set once at runtime) ===
let language = "Nim"
let version: float = 2.0
# language = "Python"  # Compile error: cannot reassign a 'let' variable

echo "language = ", language
echo "version = ", version

# === const: compile-time constants ===
const Pi = 3.14159265
const MaxSize = 1024
const Greeting = "Hello from Nim"

echo "Pi = ", Pi
echo "MaxSize = ", MaxSize
echo "Greeting = ", Greeting

# === Basic types ===
var wholeNumber: int = 42
var decimal: float = 3.14
var flag: bool = true
var letter: char = 'A'
var text: string = "Hello"

echo "wholeNumber = ", wholeNumber
echo "decimal = ", decimal
echo "flag = ", flag
echo "letter = ", letter
echo "text = ", text

# === Integer types with specific sizes ===
var small: int8 = 127
var medium: int16 = 32_000
var large: int64 = 9_000_000_000
var positive: uint = 42

echo "small (int8) = ", small
echo "medium (int16) = ", medium
echo "large (int64) = ", large
echo "positive (uint) = ", positive

# === Float types ===
var single: float32 = 1.5
var double: float64 = 1.123456789012345

echo "single (float32) = ", single
echo "double (float64) = ", double

This first example covers the fundamentals: the three declaration keywords, type inference, and all of Nim’s primitive types. Note that let and const both create immutable values, but const is evaluated at compile time — it must be computable without running the program. Use let for values determined at runtime and const for true constants.

Type Conversions and Distinct Types

Nim does not perform implicit conversions between types. You must be explicit, which prevents subtle bugs common in languages with automatic coercion.

Create a file named variables_types.nim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# === Explicit type conversions ===
let intVal = 42
let floatVal = float(intVal)
let backToInt = int(3.99)  # Truncates to 3

echo "int to float: ", intVal, " -> ", floatVal
echo "float to int (truncates): 3.99 -> ", backToInt

# === String conversions ===
let number = 256
let numStr = $number           # $ converts any type to string
let piStr = $3.14159
let boolStr = $true

echo "int to string: ", numStr
echo "float to string: ", piStr
echo "bool to string: ", boolStr

# Parsing strings to numbers
import std/strutils

let parsed = parseInt("100")
let parsedFloat = parseFloat("2.718")
echo "string to int: ", parsed
echo "string to float: ", parsedFloat

# === Distinct types for safety ===
type
  Meters = distinct float
  Kilometers = distinct float

proc `$`(m: Meters): string = $float(m) & "m"
proc `$`(k: Kilometers): string = $float(k) & "km"

let distance: Meters = 500.0.Meters
let road: Kilometers = 3.5.Kilometers

# let mistake: Meters = road  # Compile error! Cannot mix distinct types
echo "distance = ", distance
echo "road = ", road

# === Enumerations ===
type
  Direction = enum
    North, South, East, West

  Color = enum
    Red = "red"
    Green = "green"
    Blue = "blue"

var heading = North
echo "heading = ", heading

for c in Color:
  echo "color: ", c

# === Tuples and sequences ===
let point: tuple[x: int, y: int] = (x: 10, y: 20)
echo "point = (", point.x, ", ", point.y, ")"

var numbers: seq[int] = @[1, 2, 3, 4, 5]
numbers.add(6)
echo "numbers = ", numbers
echo "length = ", numbers.len

# === Arrays (fixed-size) ===
var grid: array[3, string] = ["alpha", "beta", "gamma"]
echo "grid = ", grid
echo "grid[0] = ", grid[0]

Nim’s distinct types are particularly powerful — they create new types that are incompatible with their base type, catching unit mismatch errors at compile time rather than at runtime. The $ operator is Nim’s universal “convert to string” convention, and you can define it for your own types.

Running with Docker

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

# Run the variables example
docker run --rm -v $(pwd):/app -w /app nimlang/nim:alpine nim c -r variables.nim

# Run the types example
docker run --rm -v $(pwd):/app -w /app nimlang/nim:alpine nim c -r variables_types.nim

Expected Output

Output from variables.nim:

count = 10
name = Nim Language
language = Nim
version = 2.0
Pi = 3.14159265
MaxSize = 1024
Greeting = Hello from Nim
wholeNumber = 42
decimal = 3.14
flag = true
letter = A
text = Hello
small (int8) = 127
medium (int16) = 32000
large (int64) = 9000000000
positive (uint) = 42
single (float32) = 1.5
double (float64) = 1.123456789012345

Output from variables_types.nim:

int to float: 42 -> 42.0
float to int (truncates): 3.99 -> 3
int to string: 256
float to string: 3.14159
bool to string: true
string to int: 100
string to float: 2.718
distance = 500.0m
road = 3.5km
heading = North
color: red
color: green
color: blue
point = (10, 20)
numbers = @[1, 2, 3, 4, 5, 6]
length = 6
grid = ["alpha", "beta", "gamma"]
grid[0] = alpha

Key Concepts

  • Three mutability levelsvar for mutable variables, let for runtime immutable bindings, const for compile-time constants. Prefer let and const over var when possible.
  • Type inference — Nim infers types from assigned values, so let x = 42 is the same as let x: int = 42. Explicit annotations are optional but useful for documentation.
  • No implicit conversions — Nim requires explicit type conversions like float(intVal) or int(floatVal), preventing silent data loss from automatic coercion.
  • The $ operator — Nim’s universal “to string” conversion. Works on all built-in types, and you can define it for your own types.
  • Distinct types — Create new types incompatible with their base type using distinct, catching unit and domain errors at compile time.
  • Sized numeric types — Beyond int and float, Nim offers int8, int16, int32, int64, uint, float32, and float64 for precise control over memory layout.
  • Sequences vs arraysseq[T] is a growable, heap-allocated list; array[N, T] is a fixed-size, stack-allocated collection. Use arrays when the size is known at compile time.
  • Enumerationsenum types define a set of named values, with optional string representations, and are iterable with for.

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