Live data from Hacker News

Go structs are copied on assignment (and other things about Go I'd missed)

jvns.ca

151–160 of 176 posts

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#151

Earlier quoted context omitted.

> In the mutation example you suggest, if a reference to x isn't being passed into foo, how could foo modify x The important point is that it's not "a reference to x" that gets passed, it's a copy of x's value. x's value, like the value of all Python variables, is a reference to some object. The same thing applies to setting variables in Python in general: x = {1:2} # x is a new variable that references some dict y =…

This is what confuses me; it sounds like what you're saying is Python isn't pass-by-reference, it's pass-by-value, and that value is sometimes a reference? Honestly, "x's value, like the value of all Python variables, is a reference to some object" makes me think it's more accurate to call Python pass-by-reference only.

Pass-by-reference means that your callee gets a reference to your local variables, and can modify them. This is impossible in Python. Pass by value means that your callee gets the values of your local variables and can't modify them. This is how Python functions work.

What those values represent and how they can be used is a completely different topic. Take the following code:

  x = "/dirs/sub/file.txt"
  with open(x, "w") as file:
    file.write("abc")
  foo(x)
  with open(x, "r") as file:
    print(file.read_all()) #prints "def" 

  def foo(z):
    with open(z, "w") as file:
      file.write("def")
      
Here x is in essence a "reference to a file". When you pass x to foo, it gets a copy of that reference in z. But both x and z refer to the same file, so when you modify the file, both see the changes. The calling convention is passing a copy of the value to the function. It doesn't care what that value represents.

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#152
post #123

Earlier quoted context omitted.

"In the mutation example you suggest, if a reference to x isn't being passed into foo, how could foo modify x?" Because it is passing a pointer by value under the hood. This is the part that messes everyone up. Passing pointers by value is not what passing by reference used to mean. And it matters, precisely because that is extremely realistic Python code that absolutely will mess you up if you don't understand exact…

Sure, but in the traditional sense of pass-by-reference you could say it's just always pass-by-value, and that value is always a reference. It's just not a helpful thing to say. (In those traditional pass by reference languages, was it impossible to pass a scalar to a function?) Passing a pointer-by-value similarly seems to be avoiding the point; if you tell me Python is always pass-by-value I'll expect an object I p…

> Passing a pointer-by-value similarly seems to be avoiding the point; if you tell me Python is always pass-by-value I'll expect an object I pass to a function to be a copy & not a reference, thus not be able to be mutated, and that's not the case.

That would be a misunderstanding. It would only make sense if you think Python variables are Python objects. They are not: Python variables are pointers to objects. The fact that assignment to a variable never modifies the object pointed to by that variable is a consequence of that, and doesn't apply just to passing that variable to a function.

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#153
post #140

Earlier quoted context omitted.

My point is the same for int as for vector . There is 0 difference in the C++ calling convention between passing a vector and a vector : they both copy an object of the parameter type. Of course, copying a 1000 element vector is much slower than copying a single pointer, but the difference is strictly the size of the type. The copying occurs the same way regardless. This is also the reason foo(char) is less overhead…

Your point rather misses the mark. Your vector is a red herring. The distinction I'm making is between passing a (vector )* and a vector , because those two objects have radically different sizes, and the distinction can and does create severe performance issues. And yet, pointers are still different from references: with a reference, you don't even need your object to have a memory address.

HN markup ate my *... Yes, I'm also talking about vector and vector*. They are indeed of radically different sizes, and the consequences of copying one are very different from the consequences of copying the other.

But this doesn't change the fact that they are both passed-by-value when you call a function of that parameter type.

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#154

Earlier quoted context omitted.

This is what confuses me; it sounds like what you're saying is Python isn't pass-by-reference, it's pass-by-value, and that value is sometimes a reference? Honestly, "x's value, like the value of all Python variables, is a reference to some object" makes me think it's more accurate to call Python pass-by-reference only.

Pass-by-reference means that your callee gets a reference to your local variables, and can modify them. This is impossible in Python. Pass by value means that your callee gets the values of your local variables and can't modify them. This is how Python functions work. What those values represent and how they can be used is a completely different topic. Take the following code: x = "/dirs/sub/file.txt" with open(x, "w…

So to be very clear:

  def foo(x):
    x['a'] = 1
  
  y = {'b': 2}
  foo(y)
  print(y)
foo can modify the object y points to, but it can't make y point to a different object? Is that what "This is impossible in Python" is referring to?

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#155

Earlier quoted context omitted.

Pass-by-reference means that your callee gets a reference to your local variables, and can modify them. This is impossible in Python. Pass by value means that your callee gets the values of your local variables and can't modify them. This is how Python functions work. What those values represent and how they can be used is a completely different topic. Take the following code: x = "/dirs/sub/file.txt" with open(x, "w…

So to be very clear: def foo(x): x['a'] = 1 y = {'b': 2} foo(y) print(y) foo can modify the object y points to, but it can't make y point to a different object? Is that what "This is impossible in Python" is referring to?

Yes.

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#156
post #69

Earlier quoted context omitted.

This isn't something that unsual though, from some oldies to newer ones, Delphi, Oberon variants, Modula-3, Common Lisp, Eiffel, D, Swift, and eventually Java (when value class and value record finally lands, EA available) It is the focus on managed scripting languages, that trips people up, when they finally change to one of those compiled ones.

> and eventually Java Wait, it's been a looong time since I did Java. What am I missing here; what does Java do differently from C#? (C# is my daily driver so I'm quite familiar with its handling of value and reference types)

Currently it does better escape analysis than the CLR is capable of, specially if using specific JVMs like GraalVM, Azul, as Java enjoys choice in JVM implementations. Apparently .NET 9 will have better escape analysis.

Additionally, there is the Project Valhala, whose goal is to add value types to Java, and there are now the first set of early access releases,

https://jdk.java.net/valhalla/

It has taken a while (about 10 years now) to get right, because they want to keep all existing ecosystem running on top of a Valhala improved JVM in a transparent way, and adding value types without changing the Java ABI from all stuff on Maven Central and private repos, is an herculean task.

Ideally Java's should have taken into account systems languages with GC that predated it, given the quoted influences on its design, better latter than never I guess.

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#157
post #148
post #118

Earlier quoted context omitted.

LLVM will transform your mutable program into immutable one anyway because otherwise it's much harder if not impossible to write a lot of optimizations or validations. You can collapse a lot of the additional copies at compile time even for C

That's not really the same thing though - that's a choice of representation for optimization purposes. LLVM won't turn a pointer access into a value copy - it can't, because that would change program behavior. The distinction here is that in a lot of cases, you really do want to edit data in one location and there's no good reason for it to be copied except that it makes the program easier to reason about (exceptions…

I seem to recall that in many cases it is in fact permitted for C compiler (probably C++ as well) to turn pointer access into value copy so long as the rather flexible semantics are still kept.

The main saving grace is that usually you're only dealing with register copies...

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#158

Earlier quoted context omitted.

Don't you think, as others in this thread are saying, that one reason people don't ask questions is because they're afraid to admit their lack of knowledge? We've probably all been in one of those meetings where something is being proposed, and everyone is nodding vaguely along but you're not sure the proposal is correct. Do you ask a clarifying question (and risk seeming foolish for not knowing something obvious) or…

> Do you ask a clarifying question (and risk seeming foolish for not knowing something obvious) or just let it pass because everyone else seems happy with it? Two different kinds of foolish here: etiquette or technical In some meetings if it's management and senior leadership grilling a design, if some unrelated IC butts in with a bunch of questions that are 5 steps behind everyone else it's sort of a faux pas

Right -- there isn't a single answer, it's very context dependent. I do think many ICs lean too far in the direction of keeping quiet, though.

(Managers too, come to think of it. You can feel overawed by skilled ICs and chicken out of asking naive but crucial questions.)

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#159
post #147

Earlier quoted context omitted.

> The classic example of "pass by copy-reference is less expressive" is you can't have pass a reference to number and have the caller modify it. This is really not true. Depending on how your language implements pass-by-reference, you can pass a reference to an int without boxing in one of two ways: either pass a pointer to the stack location where the int is stored (more common today), or simply arrange the stack in…

Hmmm... yeah that's a good point. Though I would contend that the fact that languages do not do this is indicative of... something.

I would guess that the main reason is that the on-stack way only works for local variables. If you want to pass anything else by reference, you need to use some kind of address to it, since it's not in the caller's stack anyway.

Re: Go structs are copied on assignment (and other things about Go I'd missed)

#160

Earlier quoted context omitted.

In principle, the answer should be (if we ignored the language community and the model of the stdlib, which we shouldn't do), that structs should be used for most things, and classes only when they are needed. There's nothing a class can do that a struct can't, and structs are not automatically allocated in the heap, so they take some pressure off the GC. Go showed that you can have a fully managed GC language where…

There is no free lunch in software :) > using the syntax of C pointers (but without pointer arithmetic) Go structs, when they have their address taken, act the same way object references do in C# or Java (that is, *MyStruct). Taking an address of a struct in Go and assigning it to a location likely turns it into a heap allocation. Even when not, it is important to understand what a stack in Go is and how it is implem…

I believe the Go design of having all types be structs and supporting explicit pointers to them is separate from their managed stack design, and both are separate still from the GC design.

You can have value types as the base type, and support (managed) pointers to the value types freely in a runtime like .NET or the JVM. I tend to think the data type design is better in Go, but the coroutine implementation and simplistic mark-and-sweep GC are inferior.

I'll also note that I should have checked how things had changed in C#. I hadn't used it sine C# 4 or something, when the ref keyword was limited to method parameters. Thanks for explaining the wealth of improvements since. This makes it indeed a core type of pointer, instead of being limited to a parameter passing scheme.

And yes, you're right that there exist many c# communities with different standards of use, and some are much more low level than Go can even... Go. I was talking mostly about the kind of things you'd find as recommendations from official MS docs or John Skeet answers on SO when I was saying "C# community", but you're absolutely right that this is a limited view and doesn't accurately encompass even some major forces in the ecosystem.

Post reply on HN