What is the model of value vs. reference in Nim?

By default the model is of passing data by value. When you create a var of a specific type, the compiler will allocate on the stack the required space for the variable. Which is expected, as Nim compiles to C, and complex types are just structures. But like in C or C++, you can have pointers too. There is the ptr keyword to get an unsafe pointer, mostly for interfacing to C code, and there is a ref to get a garbage collected safe reference (both documented in the References and pointer types section of the Nim manual).

However, note that even when you specify a proc to pass a variable by value, the compiler is free to decide to pass it internally by reference if it considers it can speed execution and is safe at the same time. In practice the only time I've used references is when I was exporting Nim types to C and had to make sure both C and Nim pointed to the same memory. Remember that you can always check the generated C code in the nimcache directory. You will see then that a var parameter in a proc is just a pointer to its C structure.

Here is an example of a type with constructors to be created on the stack and passed in by value, and the corresponding pointer like version:

type
  Person = object
    age: int
    name: string

proc initPerson(age: int, name: string): Person =
  result.age = age
  result.name = name

proc newPerson(age: int, name: string): ref Person =
  new(result)
  result.age = age
  result.name = name

when isMainModule:
  var
    a = initPerson(3, "foo")
    b = newPerson(4, "bar")

  echo a.name & " " & $a.age
  echo b.name & " " & $b.age

As you can see the code is essentially the same, but there are some differences:

  • The typical way to differentiate initialisation is to use init for value types, and new for reference types. Also, note that Nim's own standard library mistakes this convention, since some of the code predates it (eg. newStringOfCap does not return a reference to a string type).
  • Depending on what your constructors actually do, the ref version allows you to return a nil value, which you can treat as an error, while the value constructor forces you to raise an exception or change the constructor to use the var form mentioned below so you can return a bool indicating success. Failure tends to be treated in different ways.
  • In C-like languages theres is an explicit syntax to access either the memory value of a pointer or the memory value pointed by it (dereferencing). In Nim there is as well, and it is the empty subscript notation ([]). However, the compiler will attempt to automatically put those to avoid cluttering the code. Hence, the example doesn't use them. To prove this you can change the code to read:

    echo b[].name & " " & $b[].age

    Which will work and compile as expected. But the following change will yield a compiler error because you can't dereference a non reference type:

    echo a[].name & " " & $a[].age

  • The current trend in the Nim community is to get rid of single letter prefixes to differentiate value vs reference types. In the old convention you would have a TPerson and an alias for the reference value as PPerson = ref TPerson. You can find a lot of code still using this convention.

  • Depending on what exactly your object and constructor need to do, instead of having a initPerson returning the value you could also have a init(x: var Person, ...). But the use of the implicit result variable allows the compiler to optimise this, so it is much more a taste preference or requirements of passing a bool to the caller.

It can be either.

type Student = object ...

is roughly equivalent to

typedef struct { ... } Student;

in C, while

type Student = ref object ...

or

type Student = ptr object ...

is roughly equivalent to

typedef struct { ... } *Student;

in C (with ref denoting a reference that is traced by the garbage collector, while ptr is not traced).