Syntax, Statements & Expressions

Nim reads like pseudocode because there is very little punctuation between keywords and values. This lesson covers the layout rules, the difference between a statement and an expression, the literal forms, and the diagnostics the compiler gives you when something is wrong.

Layout and Statements

Nim is indentation-based: a colon opens a block, and everything indented under it belongs to that block. Beyond that rule the syntax is deliberately quiet — no mandatory semicolons, no parentheses around conditions, and no braces around bodies.

Blocks by Indentation

Indentation must be consistent inside one block. Mixing tabs and spaces in the same file is the most common cause of a confusing invalid indentation error, so configure your editor to insert spaces (the community style uses two).

# A colon opens a block; the indented lines below it are the body.
for i in 1 .. 3:
  if i mod 2 == 0:      # nested block, one level deeper
    echo i, " is even"
  else:
    echo i, " is odd"

# Parentheses are optional around conditions and around arguments when the
# call is unambiguous. These two statements are identical:
echo(1 + 2)
echo 1 + 2              # preferred form for a command with one argument

Separators, Not Terminators

A newline ends a statement; a semicolon only separates two statements placed on one line. Compact lines are fine in small doses, but a long chain usually means you should introduce a name for the intermediate value.

let a = 1; let b = 2   # semicolon separates two statements on one line
echo a + b             # newline terminates; no semicolon needed

# An operator at the end of a line continues the expression on the next line.
let total = 1 +
            2 +
            3
echo total             # 6

Comments That Nest

Nim has three comment forms, and the block form nests. Documentation comments use ## and are extracted by nim doc, so they describe the contract for callers rather than private notes for yourself.

# Ordinary comment: ignored by the compiler and by 'nim doc'.
## Doc comment: rendered into HTML documentation, so write it for callers.

#[ Block comment.
   Unlike C, blocks nest: #[ inner ]# stays inside the outer comment. ]#

proc twice(x: int): int =
  ## Doubles 'x'. The doc comment states the contract, not the mechanics.
  result = x * 2       # 'result' is the implicit return variable of every proc

Expressions and Literals

Almost everything is an expression in Nim, which is why if, case and block can produce a value instead of only steering control. Literals carry their type in their spelling whenever the default is not what you want.

Literal Forms

echo 42            # int literal
echo 0b1010'u8     # 10 — binary digits with an explicit unsigned 8-bit type
echo 0xFF'u8       # 255 — hexadecimal keeps bit-level code readable
echo 1_000_000     # underscores group digits: 1000000
echo 3.14          # float literal (float64)
echo 2.0'f32       # force 32-bit float when memory layout matters
echo 'x'           # char literal: a single character in single quotes
echo "text"        # string literal
echo true          # bool literal

Blocks That Produce Values

Because these constructs are expressions, you can assign the result of a decision directly. This removes the "declare an empty variable, then fill it in from every branch" pattern common in older languages.

let grade = if 87 >= 90: "A" else: "B"   # 'if' as an expression
echo grade                                # B

let label = case 2                        # 'case' as an expression
  of 1: "one"
  of 2: "two"
  else: "many"
echo label

# A labeled block can 'break' with a value — a structured early exit.
let parsed = block found:
  for word in ["no", "7", "8"]:
    if word.len == 1 and word[0] in {'0' .. '9'}:
      break found word          # the block yields this string
  "none"                         # fallback value of the block expression
echo parsed                     # 7

String Interpolation

Two idioms replace manual concatenation: the & operator joins strings, and the &"...{expr}..." form interpolates. Interpolation wins whenever a sentence mixes fixed text with several values.

import std/strformat
let name = "Ada"
let version = 2.0

echo "Hello, " & name & "!"      # concatenation with the & operator
echo &"Hello, {name}!"           # interpolation: same result, easier to read
echo &"{name} runs Nim {version:.1f}"   # format specs work inside { }

# Naming a format string once keeps long messages maintainable.
const report = "user={name} version={version}"
echo &report                     # const strings can be interpolated too

Reading Compiler Diagnostics

Nim reports a code, a location and a plain-language explanation. Learning to skim that output saves more time than memorizing syntax.

The Shape of an Error

# Each line names a diagnostic family, then the object it concerns.
# Error: undeclared identifier: 'naem'                    -> a typo, not a missing import
# Error: type mismatch: got 'string' but expected 'int'   -> a wrong literal or argument
# Hint: 'x' is declared but not used                      -> dead code: delete it
# Warning: ... is deprecated                              -> the message names the replacement

Traps Worth Knowing Now

SymptomCauseFix
invalid indentationTabs mixed with spaces, or an inconsistent stepOne space step per block, never tabs
expression expectedAn operator ended the lineIndent the continuation line
undeclared identifierThe symbol is not imported, or is misspelledAdd import std/<module>, then check spelling
An unused variable still compilesThe compiler emits a hint, not an errorRead the hints — they catch real bugs early

Practice

Write a program that converts a Celsius literal to Fahrenheit with a let, formats the result through interpolation, and prints it with a single echo. Then reintroduce a deliberate typo and read the compiler output carefully, so the diagnostic shape becomes familiar. Continue with Types & Values.