Skip to content
pmetras edited this page Apr 26, 2020 · 24 revisions

Nim Tips and tricks

A mix of bits of code, cookbook recipes, deep knowledge, tips and tricks about Nim programming.

Compilation

Syntax

Check syntax

Use nim check myfile.nim to check your program for errors, without code generation. This makes the process quicker.

Using reserved words as identifiers

You can use the slanted quote string syntax (stropping) if you need to use a reserved word as an identifier.

var `type`: int

Conditional compilation

Defining a compile time symbol

You want to define useSomeFeature at compilation time and use it in the code.

  • Do it when you compile: $ nim c -d:useSomeFeature myApp
  • Put it in the yourfilename.nims file located in the same directory as your source file yourfilename.nim as switch("define", "useSomeFeature")
  • Put it in the nim.cfg file located in the same directory as source file and write define:useSomeFeature
when defined(useSomeFeature):
  const someSubFeature = true
  type idType = int32
  var defaultUserName = "Frank"
else:
  type idType = int16
  var defaultUserName = "Molly"

# `when` does not open a new scope, so `idType` and `defaultUserName` are available here.

when isMainModule:
  # this code is ran only when this module is the main compilation module
  echo "This module was compiled on ", CompileDate, " at ", CompileTime

Errors

Error: attempting to call routine: 'a'

Nim likes spaces. This is a quirk of the command syntax where a +b is evaluated as a(+(b)). Use spaces, preferably, like a + b or else a+b, but don't mix space and no-spaces usage.

Error: expression '...' has no type (or is ambiguous)

  • Have you tried to discard the value from a proc that does not return any value?
proc noReturnValue =
  echo "Foo!"

discard noReturnValue()

Error: '...' doesn't have a concrete type, due to unspecified generic parameters.

  • A inner proc "inherits" the including proc generics declaration and you don't need to re-define them.
proc foo[T](x: T) =
  proc bar[T](y: T) =    <== Error is here
    echo "In bar"
  echo "In foo"

You must write:

proc foo[T](x: T) =
  proc bar(y: T) =    # Generic type T is defined by englobing proc
    echo "In bar"
  echo "In foo"

undeclared field: 'major' for type foo.Version

To access fields of a public object, these fields must be public too.

This is causing the error, where Version is defined in foo.nim:

type
  Version* = object
    major: int

You must write:

type
  Version* = object
    major*: int

Tests

How to test that the compiler will reject a piece of code?

Use compiles.

import unittest

suite "valid syntax":
  test "syntax errors":
    let a = "foo"
    let b = 5
    assert not compiles(let c = a + b), "Can't mix string and int"

Enhance documentation with examples

You can provide examples of in your documentation, with runnableExamples. When the documentation is extracted from the source with $ nim doc myfile.nim, these examples are extracted and put in a temporary file, compiled and tested. This is a good way to provide examples for your API and check that they are still valid.

proc isOdd(i: int): bool =
  ## Test if its argument is odd.
  runnableExamples:
    assert 5.isOdd, "5 is an odd number"
    assert not isOdd(3 * 2), "6 is not an odd number"

  result = i mod 2 == 1

Templates

Templates overloading

Like proc or funct, template follow overloading rules. You can use normal types for parameters but they can have meta-types too: typed, untyped and typedesc.

There are lazy type resolution rules for untyped parameters.

For typed parameters, you can use parameter constraints to reduce the scope of the parameters.

template foo(x: typed{lit}) =
  echo "foo matched the literal '", x, "'"

template foo(x: typed{ident}) =
  echo "foo matched the identifier '", x, "'"

Template scopes

You have defined a dsl: and you want to define an options: template that can be used only within dsl:. How do you do this? Use template scopes and define options within dsl, like following:

template dsl*(body: untyped) =
  template options(body: untyped) =
    # Here define the options code...
  # Here define the dsl code...

Note that you only need to make the englobing template public with the *.

Macros

Dump macro expansion

expandMacros shows how a macro call is expanded at compilation time. It's very useful for debugging macros...

dumpTree can also be used to print how a block of code is parsed at compile time.

import macros

let x, y = (4, 8)
expandMacros:
  echo "x > y is expanded to ", x > y
dumpTree:
  echo "x > y is expanded to ", x > y

Output

Echo to stderr

echo writes to stdout. If you want to print to stderr use write and writeLine

writeLine(stderr, "Write to stderr")

How do I echo in a {.noSideEffect.} proc?

You can use debugEcho for this.

Enums

Converting string to enum

Strings can be converted to enum using parseEnum from strutils. Beware that a ValueError is raised if the enum is not found. If you don't want to manage the exception, provide a default with parseEnum.

import strutils

type
  Fruit = enum
    Apple,
    Banana,
    Cherry

  let fruit = parseEnum[Fruit]("cherry")
  let pineapple = parseEnum[Fruit]("pineapple", Banana)

Tuple

Enumerate the fields of a tuple or object.

fields and fieldPairs will help you discover the fields and values of a tuple or object. But as Nim is strongly type, you can't build a result mixing types. Either use conditional compilation for different code paths or use overloading.

type
  Custom = object
    foo: string
    bar: bool

proc `$`(x: Custom): string =
  result = "Custom:"
  for name, value in x.fieldPairs:
    when value is bool:
      result.add("\n\t" & name & " is " & $value)
    else:
      result.add("\n\t" & name & " \'" & value & "\'")

proc `$1`(x: string): string = $x
proc `$1`(x: bool): string = $x

proc `$1`(x: Custom): string =
  result = "Custom:"
  for name, value in x.fieldPairs:
    result.add("\n\t" & name & " is " & `$1`(value))

let o = Custom(foo: "Hi there!", bar: false)

echo "o=", $o
echo "o1=", `$1`(o)

Language details

Shallow vs. DeepCopy

import vs include

import imports the public symbols (the ones with * after the name) from the imported module. You can't access the non-public symbols. import lines can be used only at the top level of a module.

include replace the include line with the content of the included file. You can access public and non-public symbols as they are part of the code now. include is mainly used to break big Nim files into smaller ones. include statements are not limited to the top level of module.

Clone this wiki locally