nim-style-guide
Skill4 starsChanged 3 months agoMITView on GitHub
Write clear, consistent Nim code in a simple stdlib-aligned style, covering imports, naming, proc vs func vs template choices, local variables, constructors, formatting, and control flow. Use when writing new Nim code or reviewing a Nim module for readability, consistency, and low-noise style decisions.
1 other project uses a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- planetis-m/skillsedited, first seen Jul 16, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 1
- planetis-m/skills★ 5999% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository18 other files it gives its agents.
- atlas-package-clonerSkill
- buildSkill
- docs-genSkill
- format-codeSkill
- nim-api-designSkill
- nim-c-bindingsSkill
- nim-code-organizationSkill
- nim-c-wrappersSkill
- nim-debuggingSkill
- nim-defect-analysisSkill
- nim-doc-commentsSkill
- nim-error-handlingSkill
- nim-fuzzingSkill
- nim-mcp-toolsSkill
- nimonypluginsSkill
- nim-ownership-hooksSkill
- nim-testingSkill
- run-testsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
38 rules. About 0 are common, 12 are distinctive, and 26 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈12
Distinctive · estimate
This author's own choices. Worth a look.
26
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 13
Keep lines at or below 100 characters.
- Distinctive: line 14
Use ordinary spacing instead of aligning columns by hand.
- Very specific: line 15
Write range operators compactly: a..b, a..<b, and a..^b.
- Very specific: line 23
Use std/... imports for stdlib modules.
- Very specific: line 25
Use from foo import bar, baz when you only need a small API slice.
- Very specific: line 28
Use qualified module.symbol access only to resolve genuine name conflicts between imported modules.
- Very specific: line 35
Use ALL_UPPERCASE only when preserving names from a C or C++ wrapper.
- Very specific: line 36
Use normal word casing such as parseUrl and httpStatus.
- Very specific: line 37
Do not stutter the type name into its fields.
- Very specific: line 38
Use short names for parameters and locals that echo the type, as in inv: Inventory or s: Session.
- Very specific: line 46
Use established stdlib names when behavior matches: initX, newX, find, contains, add, cmp, len, cap, items, pairs, incl, and excl.
- Very specific: line 56
Use func for pure helpers and pure accessors when checked purity helps.
- Very specific: line 57
Use template when call-site substitution, lazy evaluation, or a control-flow abstraction is required.
- Very specific: line 59
Prefer proc and func over method.
- Distinctive: line 60
Prefer top-level helpers for reusable logic.
- Distinctive: line 61
Use a nested proc when the logic is truly local or when you want a closure.
- Very specific: line 63
Use macro only when syntax transformation is required.
- Distinctive: line 68
Prefer compact wrapped calls over one-argument-per-line call blocks.
- Distinctive: line 69
Use UFCS when it reads like an accessor.
- Very specific: line 70
Write typeof(x), not the historical type(x).
- Very specific: line 71
Write enum values unqualified (north, not Direction.north).
- Very specific: line 72
Use let by default.
- Very specific: line 73
Use var only for values that mutate.
- Distinctive: line 74
Keep local declarations close to first use.
- Distinctive: line 75
Keep public and reusable types at module scope.
- Very specific: line 105
Write x notin items, not not x in items.
- Very specific: line 118
Use """...""" triple-quoted literals instead of "a\n" & "b\n" concatenation for multiline values.
- Very specific: line 120
Start with a newline right after """.
- Distinctive: line 127
Use structured control flow.
- Very specific: line 128
If the body is exactly one expression, end with that expression, as in func len(b: Bag): int = b.items.len
- Distinctive: line 131
When a loop finds the return value, return it directly instead of using a flag.
- Very specific: line 132
Do not use continue. Put the remaining loop body inside a condition.
- Very specific: line 137
Start with proc. Switch to func for a useful purity contract, template for call-site substitution, and macro for syntax transformation.
- Very specific: line 140
Write imports and names. Use std/...
- Very specific: line 141
Use std/... imports, narrow imports when practical, and keep names in normal Nim casing.
- Distinctive: line 143
Use structured control flow.
- Very specific: line 145
Use let by default, keep locals near first use, and keep reusable helpers at module scope.
- Distinctive: line 147
Remove unused imports, dead helpers, column alignment, and stretched call formatting.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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 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.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

