Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

NOTE: I am not asking about difference between pointer and reference, and for this question it is completely irrelevant.

One thing I couldn't find explicitly stated -- what model does Nim use?

Like C++ -- where you have values and with new you create pointers to data (in such case the variable could hold pointer to a pointer to a pointer to... to data)?

Or like C# -- where you have POD types as values, but user defined objects with referenced (implicitly)?

I spotted only dereferencing is automatic, like in Go.

Rephrase. You define your new type, let's say Student (with name, university, address). You write:

var student ...?
  1. to make student hold actual data (of Student type/class)
  2. to make student hold a pointer to the data
  3. to make student hold a pointer to a pointer to the data

Or some from those points are impossible?

like image 758
greenoldman Avatar asked Feb 28 '14 13:02

greenoldman


2 Answers

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.
like image 160
Grzegorz Adam Hankiewicz Avatar answered Oct 23 '22 15:10

Grzegorz Adam Hankiewicz


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).

like image 36
Reimer Behrends Avatar answered Oct 23 '22 15:10

Reimer Behrends