Nim Style Guide
Rules
Formatting
- Indent blocks with 2 spaces and use spaces instead of tab characters.
- Keep lines at or below 100 characters.
- Use ordinary spacing instead of aligning columns by hand.
- Write range operators compactly:
a..b,a..<b, anda..^b. Add spaces when an operand contains an operator, as ina .. -3. - In
rangetype constructors, use..:range[0..n-1]is valid;range[0..<n]is invalid. - Indent wrapped declarations, calls, and conditions one extra level.
Imports
- Use
std/...imports for stdlib modules. - Group imports from the same directory:
import std/[a, b, c],import lib/[x, y, z]. - Use
from foo import bar, bazwhen you only need a small API slice. importbrings symbols into scope without qualification.exporttakes only the module name:export baz, notexport foo/bar/baz.- Use qualified
module.symbolaccess only to resolve genuine name conflicts between imported modules.
Naming
- Types use
PascalCase. - Procs, funcs, iterators, templates, vars, and fields use
camelCase. - Constants default to
PascalCase;camelCaseis also allowed. - Use
ALL_UPPERCASEonly when preserving names from a C or C++ wrapper. - Use normal word casing such as
parseUrlandhttpStatus. - Do not stutter the type name into its fields;
Product.name, notProduct.productName. - Use short names for parameters and locals that echo the type, as in
inv: Inventoryors: Session. - Give the most-used representation the base type name. Suffix the others with
Obj,Ref, orPtr. - Suffix catchable exception types with
Errorand programming-bug defect types withDefect. - For non-pure enums, prefix values such as
pcFile. For pure enums, usePascalCase. - Prefix mutable views with
m, as inmitemsandmpairs. - Pair in-place and copied transforms as
reverseandreversed. If the copy name already exists, suffix the in-place form withIn, as inreplaceIn. - Name domain predicates subject-first, as in
fileExists, notexistsFile. - Use established stdlib names when behavior matches:
initX,newX,find,contains,add,cmp,len,cap,items,pairs,incl, andexcl. - Name a cheap, side-effect-free field getter
foo. UsegetFoowhen the operation has side effects or is not O(1). - Pair
foowithfoo=andgetFoowithsetFoo. - Accessors may share the field's name, no collision.
Proc, Func, Template, Macro
- Default to
proc. - Use
funcfor pure helpers and pure accessors when checked purity helps. - Use
templatewhen call-site substitution, lazy evaluation, or a control-flow abstraction is required. - Prefer
procandfuncovermethod. Usemethodonly when you need runtime dispatch. - Prefer top-level helpers for reusable logic.
- Use a nested proc when the logic is truly local or when you want a closure.
- A nested proc may capture outer locals. If a nested proc must stay non-capturing, mark it
{.nimcall.}. - Use
macroonly when syntax transformation is required. - Mark one-expression forwarders and field accessors
{.inline.}.
Calls, Locals, And Types
- Prefer compact wrapped calls over one-argument-per-line call blocks.
- Use UFCS when it reads like an accessor.
- Write
typeof(x), not the historicaltype(x). - Write
addr x, not the deprecatedunsafeAddr x. - Write enum values unqualified (
north, notDirection.north); qualify only on ambiguity. - Use
letby default. - Use
varonly for values that mutate. - Keep local declarations close to first use.
- Keep public and reusable types at module scope.
- Group related fields with the same type when it improves readability.
- Initialize
resultexplicitly; prefer object constructors (TypeName(field: value)) over field-by-field assignment. - Omit fields that should keep their declared defaults.
T()works only for objects; arrays usearrayWith, tuples use literals, strings/seqs use""/@[],default(T)as fallback.
Parsing-Sensitive Whitespace
These whitespace choices change how Nim parses code:
-
Attach
[to a type name.array[N, T]compiles.array [N, T]does not.- The same applies to
seq[T],Table[K, V], etc.
-
Attach
(to a callable name for a parenthesized call.foo(1, 2)passes twointarguments.foo (1, 2)passes a single(int, int)tuple. Nim treats the parenthesized comma-list as a tuple constructor.
-
Do not use command-call syntax with comparisons or nested calls.
foo 1 == 1parses likefoo(1 == 1), notfoo(1) == 1.outer(inner 1, 1)parses asouter(inner(1), 1), notouter(inner(1, 1)).
-
Use
[:T]for explicit generic parameters in UFCS calls.x.p[:T]()rewrites top[T](x).x.p[T]()parses as(x.p)[T](), not a generic instantiation.
-
Group negated compound expressions.
- Parenthesize compound operands of
not:not a or bparses as(not a) or b, so writenot (a or b)andnot (x < y). - Write
x notin items, notnot x in items.
- Parenthesize compound operands of
Line Continuation
Nim has no line-continuation character. Break expressions after an operator
or comma, or within open delimiters such as (), [], and {}. Indent the
continued line further than the statement that started it.
For constructs with a body, double indentation can distinguish continuation lines from the body.
Multiline Strings
- Use
"""..."""triple-quoted literals instead of"a\n" & "b\n"concatenation for multiline values. - Start with a newline right after
""". That newline is stripped, so the value is identical; the content aligns with the next line.
See references/multiline_strings.md.
Control Flow
- Use structured control flow.
- If the body is exactly one expression, end with that expression, as in
func len(b: Bag): int = b.items.len - Otherwise assign the return value to
resultexplicitly: as a default at the start, at the end, or once per branch. - A default
resultmay be overridden by the branch that produces the value. - When a loop finds the return value, return it directly instead of using a flag.
- Do not use
continue. Put the remaining loop body inside a condition.
Workflow
- Pick the callable kind.
Start with
proc. Switch tofuncfor a useful purity contract,templatefor call-site substitution, andmacrofor syntax transformation. Choosemethodfor runtime dispatch. - Write imports and names.
Use
std/...imports, narrow imports when practical, and keep names in normal Nim casing. - Shape the control flow. Use structured control flow. Return directly when a loop finds its result.
- Clean up locals and constructors.
Use
letby default, keep locals near first use, and keep reusable helpers at module scope. - Remove noise. Remove unused imports, dead helpers, column alignment, and stretched call formatting.
Common Mistakes
| Mistake | Why it is wrong |
|---|---|
Using method as the default callable kind | It adds runtime dispatch where a plain proc or func would usually be clearer. |
| Hiding reusable helpers inside another proc | It makes the helper harder to reuse and easier to turn into an accidental closure. |
| Writing one argument per line by default | It adds vertical noise without adding structure. |
Using var for values that never mutate | It hides which locals actually change. |
Assuming {.pure.} requires Type.value | Pure enum values may be unqualified. |
Turning every branch into an early return in a multi-step proc | It makes the normal path harder to scan. |
Using continue | A structured branch keeps the loop invariant visible. |
References
references/core_patterns.md— General style patterns for imports, callable kinds, wrapping, locals, and constructors.references/multiline_strings.md— Whitespace-preserving triple-quoted strings, joins, and dedenting.


