Skip to content

Instantly share code, notes, and snippets.

@planetis-m
Created April 14, 2026 15:59
Show Gist options
  • Select an option

  • Save planetis-m/bb79b441ebe25c13b56ba2545ec0670f to your computer and use it in GitHub Desktop.

Select an option

Save planetis-m/bb79b441ebe25c13b56ba2545ec0670f to your computer and use it in GitHub Desktop.

name: nim-error-handling description: Design clear Nim error-handling flows. Use when reviewing failure behavior, exception boundaries, or batch processing.

Nim Error Handling

Use this skill when deciding where code should raise, catch, translate, retry, or return structured failure data.

Rules

Exceptions are the default

  • Raise exceptions for all errors: invalid input, parsing failures, I/O failures, semantic violations.
  • The Nim compiler and stdlib overwhelmingly use exceptions. This is not a close call.
  • Use newException(ValueError, msg) for bad input. Use newException(IOError, msg) for I/O. Use newException(OSError, msg) for OS errors. Pick the most specific stdlib type that fits.

Catch specific types at real boundaries

  • Catch specific exception types: except ValueError, except IOError, except OSError.
  • Catch at module boundaries to translate (e.g., except OSError: raise newException(IOError, ...)).
  • Catch at orchestrator boundaries to aggregate (e.g., batch processor records per-item failures).
  • Use try/finally for cleanup, not try/except.

What not to catch

  • Do not catch CatchableError or Exception in normal code. CatchableError does not catch Defect, but Exception does — and catching bugs is wrong. Both are too broad. Catch the specific types your code actually handles.

Option[T] is for optional data, not errors

  • Use Option[T] when none is a valid outcome: search misses, optional struct fields, safe enum conversions.
  • Do not use Option[T] to avoid raising on bad input. Bad input is an error. Raise.

raises pragma

  • Add {.raises: [X].} on exported procs with a stable exception surface.
  • Add {.raises: [].} on procs guaranteed not to raise.
  • Do not annotate internal helpers by default.
  • The compiler enforces raises contracts. Keep them accurate.

Internal procs raise, boundaries catch

  • Internal step procs raise exceptions. Do not return error codes, nil, or bool to signal failure.
  • Boundaries catch, translate, or aggregate. Keep the success path straight-line between them.
  • Do not wrap every call in try/except. Let exceptions propagate to the real boundary.

Minimal Pattern

type
  BatchItem = object
    path: string
    ok: bool
    errorMsg: string

proc loadFile(path: string): string =
  if path.len == 0:
    raise newException(ValueError, "path is empty")
  result = readFile(path)

proc processBatch(paths: seq[string]): seq[BatchItem] =
  for path in paths:
    try:
      let content = loadFile(path)
      result.add BatchItem(path: path, ok: true, errorMsg: "")
    except ValueError, IOError:
      result.add BatchItem(path: path, ok: false,
        errorMsg: getCurrentExceptionMsg())

Common Mistakes

Mistake Why it is wrong
Catching CatchableError or Exception Too broad. Catch specific types.
Using Option[T] for bad input Bad input is an error. Raise.
Returning nil or error codes from internal procs Reimplements exception propagation. Just raise.
Catching in every layer Let exceptions propagate to the real boundary.
Using try/except for cleanup Use try/finally.

References

  • references/batch_preview_boundary.md
  • references/retry_classification.md

Changelog

  • 2026-04-14: Rewritten from empirical compiler/stdlib review. Exceptions are default. Catch specific types at boundaries. Option[T] is for optional data only.
  • 2026-04-11: Initial skill structure.
  • 2026-04-09: Simplified rule set.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment