Semantics of `let` assignment in Nim

150 Views Asked by At

Semantics of let assignment in Nim

I was recommended the Nim programming language and started reading through the Tutorial 1, the Manual and even the memory model (in particular the section about strings and seqs).

I soon encountered some strange behavior regarding let assignment which doesn't seem to make any sense at all, to the point of appearing a complete mess. I can demonstrate all the nonsense with this simple script (link to playground):

echo "first example:"

proc p =
  var msg = "Hello"
  let copy = msg
  msg.add(" World!")
  echo msg
  echo copy # does NOT change
p()

echo "\nsecond example:"

proc q =
  var msg = "Hello"
  let copy = msg
  msg.add("!")
  echo msg
  echo copy # DOES change
q()

echo "\nthird example:"

var msg = "Hello"
let copy = msg
msg.add("!")
echo msg
echo copy # does NOT change

In the first and third examples, copy retains the original value "Hello". The second example (proc q) shares a similarity with the other two cases:

  • it is similar to the first because it employs the local scope of a proc,
  • it is similar to the third because it appends the same string ("!").

For some arcane reason however, in the first and third examples copy is indeed a copy (bind by value?), whereas in the second example the modification to the variable msg propagates to the immutable copy (bind by reference?).

What is going on? How can we make sense of this? Does let bind by value or by reference? If it make sense somehow, why is it not explained clearly and in depth in the official guides?

2

There are 2 best solutions below

2
Ethosa On

I found strange thing:

msg.add("!") # work in procedures (1 and 2 examples, but not 3)
msg.add(" World!") # doesn't work in all examples

At top-level string variable just copied, but in procedures small changes looks like changing pointers, but big changes looks like copy ...

I'm trying to change let to var and here is not changes.

I think that reason is compiler bug.

2
xbello On

This happens if you append and the resulting string is less than 8 bytes. I. e. if you append to var msg="1" less than 7 chars, the copied let copy = msg also gets modified. Appending 7 or more and the two strings are different:

proc q =
  var original = "1"
  let copy = original
  original.add "2"
  echo original, " ", copy

q()

>>> 12 12

The addr of both are the same:

  echo original.repr, " ", copy.repr

>>> 0x7fa5ac992060"12" 0x7fa5ac992060"12"

But if you add characters enough that the final original string gets to 8 bits or more:

proc q =
  var original = "1"
  let copy = original
  original.add "234567"

  echo original, " ", copy
  echo original.repr, " ", copy.repr

  original.add "8"

  echo original, " ", copy
  echo original.repr, " ", copy.repr

q()

>>> 1234567 1234567
>>> 0x7f2f0f5cb060"1234567" 0x7f2f0f5cb060"1234567"
>>> 12345678 1234567
>>> 0x7f2f0f5cb1b0"12345678" 0x7f2f0f5cb060"1234567"

The original string gets a new address, and the copy is still at the initial one.