`let` 在 Nim 中的赋值语义

huangapple go评论90阅读模式
英文:

Semantics of `let` assignment in Nim

问题

以下是您要翻译的内容:

# Nim中`let`赋值的语义

有人推荐了Nim编程语言给我,我开始阅读[Tutorial 1](https://nim-lang.org/docs/tut1.html)、[Manual](https://nim-lang.org/docs/manual.html),甚至是[内存模型](http://zevv.nl/nim-memory/)(尤其是关于[strings和seqs](http://zevv.nl/nim-memory/#_strings_and_seqs)的部分)。

很快,我遇到了关于`let`赋值的一些奇怪行为,似乎毫无道理,甚至看起来一团糟。我可以用这个简单的脚本来演示所有这些荒谬之处([链接到playground](https://play.nim-lang.org/#ix=4B2x)):

```nim
echo "第一个例子:"

proc p =
  var msg = "Hello"
  let copy = msg
  msg.add(" World!")
  echo msg
  echo copy # 不会改变
p()

echo "\n第二个例子:"

proc q =
  var msg = "Hello"
  let copy = msg
  msg.add("!")
  echo msg
  echo copy # 会改变
q()

echo "\n第三个例子:"

var msg = "Hello"
let copy = msg
msg.add("!")
echo msg
echo copy # 不会改变

在第一个和第三个例子中,copy保留了原始值"Hello"。第二个例子(proc q)与其他两个案例有一些相似之处:

  • 它类似于第一个,因为它使用了proc的局部作用域,
  • 它类似于第三个,因为它附加了相同的字符串("!")。

然而,由于某种神秘的原因,在第一个和第三个例子中,copy确实是一个副本(按值绑定?),而在第二个例子中,对变量msg的修改传播到了不可变的copy(按引用绑定?)。

发生了什么?我们如何理解这个问题?**let是按值绑定还是按引用绑定?**如果有某种解释,为什么官方指南没有清晰而深入地解释这一点?


<details>
<summary>英文:</summary>

# Semantics of `let` assignment in Nim

I was recommended the Nim programming language and started reading through the [Tutorial 1](https://nim-lang.org/docs/tut1.html), the [Manual](https://nim-lang.org/docs/manual.html) and even the [memory model](http://zevv.nl/nim-memory/) (in particular the section about [strings and seqs](http://zevv.nl/nim-memory/#_strings_and_seqs)).

I soon encountered some strange behavior regarding `let` assignment which doesn&#39;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 (
(https://play.nim-lang.org/#ix=4B2x)): ```nim echo &quot;first example:&quot; proc p = var msg = &quot;Hello&quot; let copy = msg msg.add(&quot; World!&quot;) echo msg echo copy # does NOT change p() echo &quot;\nsecond example:&quot; proc q = var msg = &quot;Hello&quot; let copy = msg msg.add(&quot;!&quot;) echo msg echo copy # DOES change q() echo &quot;\nthird example:&quot; var msg = &quot;Hello&quot; let copy = msg msg.add(&quot;!&quot;) echo msg echo copy # does NOT change

In the first and third examples, copy retains the original value &quot;Hello&quot;. 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 (&quot;!&quot;).

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?

答案1

得分: 2

这种情况发生在你追加字符串后,结果字符串少于8个字节。也就是说,如果你追加到var msg = "1"的字符串少于7个字符,复制的let copy = msg也会被修改。追加7个或更多字符,这两个字符串就不同了:

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

q()

>>> 12 12

它们的addr是相同的:

echo original.repr, " ", copy.repr

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

但是如果你添加足够多的字符,使最终的original字符串达到8位或更多:

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"

original字符串会获得一个新的地址,而copy仍然在最初的地址上。

英文:

This happens if you append and the resulting string is less than 8 bytes. I. e. if you append to var msg=&quot;1&quot; 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 = &quot;1&quot;
  let copy = original
  original.add &quot;2&quot;
  echo original, &quot; &quot;, copy

q()

&gt;&gt;&gt; 12 12

The addr of both are the same:

  echo original.repr, &quot; &quot;, copy.repr

&gt;&gt;&gt; 0x7fa5ac992060&quot;12&quot; 0x7fa5ac992060&quot;12&quot;

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

proc q =
  var original = &quot;1&quot;
  let copy = original
  original.add &quot;234567&quot;

  echo original, &quot; &quot;, copy
  echo original.repr, &quot; &quot;, copy.repr

  original.add &quot;8&quot;

  echo original, &quot; &quot;, copy
  echo original.repr, &quot; &quot;, copy.repr

q()

&gt;&gt;&gt; 1234567 1234567
&gt;&gt;&gt; 0x7f2f0f5cb060&quot;1234567&quot; 0x7f2f0f5cb060&quot;1234567&quot;
&gt;&gt;&gt; 12345678 1234567
&gt;&gt;&gt; 0x7f2f0f5cb1b0&quot;12345678&quot; 0x7f2f0f5cb060&quot;1234567&quot;

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

答案2

得分: 1

我发现了奇怪的事情:

msg.add("&quot;!&quot;) # 在过程中工作(示例1和2,但不是3)
msg.add("&quot; World!&quot;) # 在所有示例中都不起作用

在顶层字符串变量中只是复制,但在过程中,小的更改看起来像是改变了指针,但大的更改看起来像是复制...

我试图将 let 更改为 var,但这里没有变化。

我认为原因是编译器的错误。

英文:

I found strange thing:

msg.add(&quot;!&quot;) # work in procedures (1 and 2 examples, but not 3)
msg.add(&quot; World!&quot;) # doesn&#39;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.

huangapple
  • 本文由 发表于 2023年7月20日 20:09:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76729702.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定