Hacker News new | comments | show | ask | jobs | submit login
There is no pass by reference in Go (cheney.net)
130 points by micah_chatt 173 days ago | hide | past | web | 66 comments | favorite



An extended explanation missing from Dave Cheney's answer is that the "reference" etymology has at least 2 meanings. This is why his explanation is virtually the same as the one 20 years ago in the C Language FAQ[1] compiled by Steve Summit.

- "reference" definition 1: an alias that cannot be null which is what a "reference type" is in Pascal and C++. This might be thought of as a stricter "computer-science" definition. The C and Go languages don't have this type of "reference".

- "reference" definition 2: a pointer variable that lets programmers change the "thing pointed to" instead of the pointer variable itself to avoid copying unnecessary bytes. This is the colloquial usage. It also doesn't help that we call the star operator the "de-REFERENCING" operator[2] instead of the "de-POINTERIZING" operator. (What are we "derefencing" if it's not a "reference"?!?)

Cheney is talking about definition 1.

[1] http://www.di-srv.unisa.it/~vitsca/LAB/C-faq.html excerpt:

  4.11:	Does C even have "pass by reference"?

  A:	Not really.  Strictly speaking, C always uses pass by value.
	You can simulate pass by reference yourself, by defining
	functions which accept pointers and then using the & operator
	when calling, and the compiler will essentially simulate it for
	you when you pass an array to a function (by passing a pointer
	instead, see question 6.4 et al.).  However, C has nothing truly
	equivalent to formal pass by reference or C++ reference
	parameters.  (On the other hand, function-like preprocessor
	macros can provide a form of "pass by name".)
[2] https://en.wikipedia.org/wiki/Dereference_operator


Indeed. Definition 2 is broadly in line with what "reference" means in general usage, while definition 1 is a term of art in the vocabulary of certain languages.

Before languages had reference types or pointers, the distinction between pass-by-reference and pass-by-value (or also pass-by-name in Lisp and Algol) told you important things about the semantics of the language, such as whether a function you called could modify the objects you gave as arguments, or incorporate them by reference into some other object, and how arguments that are expressions are handled. Some of those distinctions become blurred when pointers or references are passed by value; the distinction on how you pass them now becomes a distinction on what you pass. In practice, is there any language that passes pointers or references by reference?


It may be what reference means, but just because a language has references doesn't mean that it is passing-by-reference. There are plenty of languages that allow actual pass-by-ref, like C++. The usual litmus test is writing a function that takes two ints (not int refs!) and they're swapped at the end. In C++, you can tell because of "int&" in the signature of the function you're calling; as opposed to plain int.

In languages with macros, you get to cheat a little, and write something that looks like the above without _really_ having those semantics. However, in languages like Lisps where your macro language is extremely powerful, you might not really miss it so much.

There is a lot of convenience in _not_ having pass-by-ref! Sometimes I feel like the ardent opposition just doesn't want to admit their favorite language is missing a feature ;-) If I call a function f(a, b), I know that a, b are still pointing at the same object in e.g. Python. That is not the case in C++ (pass-by-ref) or Lisp (arbitrary macros). Predictable language semantics make code easier to reason about. That's a good thing, and it's quite plausible that the added complexity and confusion of supporting pass-by-ref isn't worth the expressiveness.

I appreciate that the term is confusing, but it does mean something. I submit that making it mean two things would be even more confusing. If it's confusing, just say "passing (an argument) _as a reference_" which is unambiguously about what you're passing, and not how you're passing it.


"If I call a function f(a, b), I know that a, b are still pointing at the same object in e.g. Python."

  def f(x):
    global a
    a = "No, you don't"

  a = "I know"
  f(a)
  print a
This counterexample may be far-sought, but it is a minimal version of what can easily happen in larger programs that have shared mutable state.

But yes, call by reference can be confusing. It also can be very useful, though. That's why many modern languages have call by reference, but also require callers to specify it at each call site. I think that gives you the best of both worlds.


Whenever a word with a general meaning is adopted, and specialized, as a term of art, a double meaning is created. I get the impression that philosophy prefers to invent new words (unless it is reaching into the darkest corners of the vocabulary.)

What has happened here is that the double meaning of 'reference' has caused some confusion over the term pass-by-reference, which is purely a term of art. So long as a person understands what gets passed and, especially, its implications, I am not particularly bothered by the mistaken usage.


Object Pascal (Delphi, Free Pascal) allows this. You can use a var (variable) parameter to pass any type by reference to a function/procedure/method. This is used quite a bit in the Win32 API interface units in the run-time library because it simplifies the usage of the API by removing the requirement for double indirection when dealing with pointer parameters that need to be updated by the callee, as well as pointers in general for other type/structure parameters that need to be updated by the callee. As the caller, instead of passing a pointer to a structure as a parameter, you simply pass the structure. There are also out parameters that work similarly, but are, obviously, output-only.

In general, you can use pointers in Object Pascal, but the language offers a lot of options for avoiding them, if you want to, and you get type-checked references in the process. Records (structs) are stack-based and pass-by-value, by default, and class references/class instances (instances are always heap-allocated) are pass-by-reference, by default. So, for the majority of applications, you can use parameters in a myriad number of ways in order to improve performance or provide the proper calling semantics, without any of the hassle of address-of or de-referencing operators.


Don't know of any mainstream Lisp with "pass by name". Which dialects have or had that?


Not the author, but they may be referring to NLAMBDA/FEXPR. (That hasn't been in Lisp since the eighties, because, well, Lisp had better tools. NLMABDA/FEXPR basically made AOT compiling Lisp impossible, and all of the use cases were much more neatly handled by a macro system.)


https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_na...

Call by name is actually a closure based mechanism under the hood. It's like lazy evaluation, but with side effects allowed:

  (let ((0 42))
        (a #(1 2 3))
    (foo (aref a (incf i))))
If foo is call by name, then it receives a thunk (basically a lexical closure) for the argument. The aref expression is then evaluated inside foo whenever foo refers to the corresponding argument. (Unfortunately, repeating the incf side effect). We could simulate this with:

   (foo (lambda () (aref a (incf i)))
with foo using (funcall arg) to get the argument's value.

Call by need, described below that, seems exactly like the insertion of delay and force. The call-by-need argument expression is basically wrapped in an implicit delay, and callee refers to the argument value using implicit force. delay and force are just additional wrapping around a closure to add a flag to squash multiple evaluations.


Ah!

I just remembered I implemented some macros to simulate call by name in the TXR solution to the "Man or boy test" task on Rosetta Code:

https://rosettacode.org/wiki/Man_or_boy_test#TXR

With these macros, Knuth's test is expressed exactly in the original form (modulo the Algol being cast into S-exp syntax).

Call by name isn't just lambda with funcall (as might be wrongly inferred from my comments in the other reply), because it supports assignment. If we pass a variable A with call by name, the caller can assign to it. This all works with the given macros, and it probably more or less corresponds to the Algol mechanisms.

(The task description is "Imitate Knuth's example in Algol 60 in another language, as far as possible." so the solutions which don't actually implement a call-by-name syntax and semantics don't go as far as this solution.)


It has been a while since I used any form of Lisp, but nlambda seems to be what I was remembering as pass-by-name (and hand-annotated as deprecated, here:) https://www.cl.cam.ac.uk/teaching/1213/ConceptsPL/l4.pdf


Thank you sir for pointing this out. I recently ran into a similar argument with an Oracle trainer whether Java has pass-by-reference semantics. The entire Java community seems adamant that Java has no pass-by-reference, but only pass-by-value. This blog post reminds me of that argument.

In Java, Python, Javascript and many other languages, objects pointers are passed by value, but the contents of which are shared between the caller and the callee, so the modification of which is visible from both sides. If we take the definition of pass-by-reference or call-by-reference semantics to mean that, then these languages are pass by reference by default. Apparently, that's not the definition, which in my opinion is rather strange that we are still stuck in a definition that predates OOP, when people mostly concerned with simple primitive values as opposed to composites.

It appears that Barbara Liskov has recognised this and coined call-by-sharing in 1974. 43 years later, practitioners are still arguing in the C++ frame of mind and failed to disseminate the more modern and suitable term of call-by-sharing. This is rather disappointing to me.


Aren't reference variables just syntactic sugar on top of pointers? I've seen this debate about pointers vs. references so many times that I'm starting to think I might be missing something.


This has always bugged me too. I don't understand why people make such a big deal about pass-by-value and pass-by-reference when the latter is essentially a small layer of syntactic sugar over the former. Anything you can do with reference variables can be done with pointers, with a few more characters (and arguably easier to read, if you're not used to reference variables), so from a semantic point of view it doesn't add anything new.

PBV and PBR seems to be a tiny distinction and yet, we have blog posts and interview questions and online debates about whether a language falls into the first or second category.


Yes.


Yap, your comment is better than the entire article. Thanks!


And Java just added to the problem.

For PR reasons, they didn't want to have "pointers" (arithmetic, etc. ew). So Java has "references".

But what happens when you call a method on a null reference? Not NullReferenceException, but NullPointerException.


>For PR reasons, they didn't want to have "pointers" (arithmetic, etc. ew). So Java has "references"

Well, it's just a rename for PR reasons. They actually don't have pointers.

>But what happens when you call a method on a null reference? Not NullReferenceException, but NullPointerException.

That's because it's the underlying pointer access that causes the exception. Doesn't mean the reference is itself a pointer -- it just abstracts over one.


> They actually don't have pointers.

They do. Everywhere.

Every Java variable of Object type is a pointer. (Also, they're all nullable....ugh.)

If Java had real references (see original comment by jasode) you'd be able to write something like C#'s int.TryParse ( https://msdn.microsoft.com/en-us/library/f02979c7(v=vs.110).... ).


>Every Java variable of Object type is a pointer.

No, it's a reference. It just has a pointer underneath.

>If Java had real references (see original comment by jasode) you'd be able to write something like C#'s int.TryParse

If Java had one specific type of references. The common use of the term though is still what Java has.


> The common use of the term though is still what Java has.

The way you are defining "reference" makes it immaterially different from "pointer".

Can a pointer be null? Yes, in C.

Can a "reference" be null? Yes, in Java.

Can a pointer be used in arithmetic operations? Yes, in C.

Can a pointer not be used in arithmetic operations? Yes, in Go.

If this is really the common definition, it isn't a useful one.


Whether a thing can be null or not is orthogonal to whether it is a pointer or a reference.

The main difference in this definition is on whether you need to dereference to access the value pointed at (like you do in C) or not.


Not sure I understand you. A concrete PHP example:

   function noop($a, $b) {
     $tmp = $a;
     $a = $b;
     $b = $tmp;
   }

   function swap(&$c, &$d) {
      $tmp = $c;
      $c = $d;
      $d = $tmp;
   }

   $first = [1, 2];
   $second = [3, 4];
   noop($first, $second);
   swap($first, $second);
   
$a and $b work much differently than $c and $d.

I would call the first two pointers (to [1,2] and [3,4]), and the latter two references (to $first and $second). What would you call them?

(Note: the latter function is impossible to write in Java.)


>I would call the first two pointers (to [1,2] and [3,4]), and the latter two references (to $first and $second). What would you call them?

In the first case, $a and $b pass the arrays by copying their values. The second example uses references.

Java can't do the change visible externally because it passes its references by value -- but that's not a necessity for all languages with references.

As the PHP manual itself says: "References in PHP are a means to access the same variable content by different names. They are not like C pointers; for instance, you cannot perform pointer arithmetic using them, they are not actual memory addresses, and so on".


They also insists Java is pass-by-value whereas technically it's pass-by-value for primitive unboxed types and call-by-sharing for others. The reason of which escapes me.


"Call-by-sharing" is just a different, and IMHO unnecessary and unhelpful, name for pass-by-value when the values happen to be pointers.

To put it another way, the inconsistency in Java isn't that primitives and objects are passed differently, it's that they're stored differently. Given how they're stored, they're passed the same way.


Since primitives are immutable have value-equality, it doesn't matter.

If your cognitive model prefers to think of everything as pass pointer by value, you think that.


I've got a credit in a game by pointing out that the reason the teams tools kept segfaulting was because the authors believed that if they had a reference they didn't need to check it for null. Unfortunately, they'd assign these references from pointers that could totally be null, and didn't error check at that point. References in C++ can totally be null.


That's undefined behavior. References in valid C++ cannot be null.


I can write a method that is valid C++, but it gets called by something that isn't valid C++. If I don't check for null references, then my method will crash. My method is valid C++, and yet it gets a null reference. So clearly, references in valid C++ can be null, and must be checked for.


"Maps and channels are not references."

But then refer to the Go team's article on maps:

  Map types are reference types, like pointers or slices, and so the value of m above is nil; it doesn't point to an initialized map. A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don't do that. To initialize a map, use the built in make function:
  
  m = make(map[string]int)
  
  The make function allocates and initializes a hash map data structure and returns a map value that points to it. The specifics of that data structure are an implementation detail of the runtime and are not specified by the language itself. In this article we will focus on the use of maps, not their implementation.
Saying maps are not passed by reference is a bit deceiving. The map's pointer location is not passed by reference.

Source: https://blog.golang.org/go-maps-in-action


IMHO saying they are is misleading as this article correctly points out nothing is, and Rob Pike himself has pointed out the same on many occasions. They are referred to as "reference types" because they contain pointers to the underlying data structure. However the thing stored in the variable is more than just a pointer, and it is passed by value.

Knowing the difference can have important ramifications; particularly in the case of a slice which has its header copied when passed to a function.


It's a question of semantics and the poorly chosen word "reference".

The roots come from C, where pretty much every book on the subject talked about the difference between passing a huge, expensive value to a function vs passing a cheap pointer "reference" to it.

And while the semantic purists will scream and holler, the fact remains that, colloquially, a pointer is a reference to something, not the something itself. And so this annoying debate rears its ugly head a few times a year and makes it into HN or Slashdot or whatever.


Nobody is debating what the word "reference" means. The debate is that "pass-by-reference" is already a different thing from passing something __as__ a reference.

I appreciate that it's confusing to someone who isn't familiar with the term, but so's most of CS. We had a term and it referred to a thing, and having it refer to two different things is worse.


And yet this is the reality we live in. No matter how much talk of "should", the reality stares you implacably in the face. This is a UX problem.

Practically speaking, the most important concept is that of cost, which the naive answer covers. The distinction only matters in a language that actually differentiates the two. Otherwise it's little more than trivia, and certainly nothing to get worked up over.


The difference between pass-as-reference and pass-by-reference is more than trivia - if you were ignorant of it in a language, you might find trouble in the future when a function decides to alter its argument values in a way that negatively affects the caller. Part of the reason we should work to clarify the language is to avoid misconceptions about behaviour based on what kind of "reference" is meant.


A language with pointers also allows a function to alter its logical (not syntactical) argument values in a way that negatively affects the caller.

There isn't much practical difference between using

    // void c_func_alters_deref_pfoo(Foo * pfoo)
    Foo foo = { ... };
    c_func_alters_deref_pfoo(&foo);
and

    // void cpp_func_alters_foo(Foo & foo)
    Foo foo = { ... };
    cpp_func_alters_foo(foo);
besides the ability to pass null or otherwise invalid pointers in the C-style version, which can (depending on situation) either simplify your API or be a source of bugs.


>A language with pointers also allows a function to alter its logical (not syntactical) argument values

What's the difference between a logical and syntactical argument?


Minor nit...

> It is not possible to create a Go program where two variables share the same storage location in memory.

It is possible to do so, because the empty struct occupies no space. For example:

    s := []struct{}{
        struct{}{},
        struct{}{},
    }
    fmt.Printf("%p %p\n", &s[0], &s[1])

    > 0x176f44 0x176f44
But that's kind of a fringe case, and I suppose one might argue that the two variables occupy no space so in a sense they don't share that space. Either way it doesn't detract from the original point of the post :)


Often, I'll deliberately give the naive "pointers are references" answer in a job interview, just to gauge the interviewer's response.

It's actually a good proxy to determining company culture. If he starts to patiently explain the difference, I'll stop him and talk about the practical implications of the concept, and we'll have a good laugh.

If he gets annoyed or stiffens and things become tense, I'll have a good proxy for what to expect should I make the mistake of working there.


Then there are those interviewers who silently cross you off the list and move on. They are probably type II people, however, so no harm done from either point of view.


By this obtuse definition of 'pass by reference', Nothing with a C-like function call stack will 'pass by reference'. Programming languages that approximate 'pass by reference' will actually pass a pointer to the called function that points to a value in the callee's stack frame. The pointer in the called function is a local variable, so changing the value of that has no effect in the callee's scope. Changing the contents of the thing pointed to is the only _meaningful_ 'pass by reference', and practically most languages have that. (including Go)


Interestingly, the term "reference type" has been removed from the Go spec in 2013: https://github.com/golang/go/commit/b34f0551387fcf043d65cd7d...

Yet some semi-official sources (like The Go Programming Language book) do refer to channels as "reference types".

TBH, I find cheney's post not fully useful without explaining what does happen behind the scenes in maps so that passing them by value doesn't incur large copies.


A map is a tiny struct containing a pointer to the bucket storage. It's always cheap to pass a map value.


Right.

I'm saying the linked blog post can be improved significantly by spending a paragraph on explaining this. Otherwise it just sounds like magic.


C# has a name for Go's object semantics: Marshal By Reference. This means that when an object features in a call, the GC pointer aka reference is passed (by value) rather than the object value itself. This reference is fundamentally different from the reference concept in C++. So the term reference is semantically overloaded and means different things in different programming languages. However, the phrase "pass by reference" is language independent and the author is correct, it doesn't have those semantics.


When teaching, rather than using a special name like "pointer", wouldn't it be better to simply explain that it's a variable that holds a memory address? Once a student (e.g. who only knows a high-level language) understands that they now have to think about memory explicitly, then the behavior of such a variable will become obvious. AFAIU there are only two special features of pointers: the dereference operation and the additive arithmetic which increments in strides according to their type.


C++:

  void f(map<int, int> m)
  {
  	m[0] = 0;	
  }

  int main() {
  	map<int, int> t;
  	t[0] = 1;
  	f(t);
  	cout << t[0] << endl;
  	return 0;
  }
prints 1.

Go:

  func f(m map[int]int) {
  	m[0] = 0
  }

  func main(){
  	var t = make(map[int]int)
  	t[0] = 1
  	f(t)
  	fmt.Println(t[0])
  }
Prints 0.

So in C++ maps are passed by value. In Go they are passed by, hmm, "not value".

Go could have made maps similar to C++ maps and then people could use a pointer to a map when they wanted that. As it stands maps are "different" which is not a big deal once you're used to it but still makes things a little weird if you're coming from C++.


Go lacks the C++ reference operator. You can't write:

    func fn(m &map[int]int) {
        m = make(map[int]int)
    }
    ...
    fn(m)
You have to write:

    func fn(m *map[int]int) {
        *m = make(map[int]int)
    }
    ...
    fn(&m)
It's very C. The caller is forced to use different syntax to pass something as a pointer/reference. In C++ and Rust, you can't see from the call whether you're passing a pointer or a reference.


The difference is in C++ rvalues coerce to const&, while in Rust they do not.

E.x. this works

    int square(const int & num) {
        return num * num;
    }
    
    int main() {
        square(5);
    }
this does not

    fn takes_ref(a: &i32) -> i32 {
        *a
    }

    fn main() {
        takes_ref(64);
    }
There are still cases where you won't be able to tell if you're using a ref or not, though:

    let x = returns_a_ref();
    takes_ref(x);


However, Go has pointers (just not pointer arithmetic):

https://tour.golang.org/moretypes/1

Does anyone know of an imperative language doesn't have references, but also doesn't have pointers? Perhaps using something like copy-on-write to pass variables to functions.


I haven't used Go before; is there a common conception that you can pass by reference in Go?


Some programmers make a liberal use of "passing by reference" (v. "passing by value") a variable when referring to passing a pointer to it (v. its value) to a function. This is common language in a C environment, where there's also no pass by ref (quick example: [0]). I'm guilty of this myself.

It's possible that this may create confusion to other people more familiar with, for example, the C++ concept of passing by reference which is passing an actual reference (v. "passing a pointer").

[0] https://www.tutorialspoint.com/cprogramming/c_function_call_...


I guess in every language with pointers, people often confuse passing the pointer as a value with passing the object as a reference.


I'm still confused and I've been working in various languages for 15 years.

If I pass a java object to a function, I'm passing a (probably) 8-byte pointer to some memory with a class tag, fields, whatever. It goes on the stack just like an 8 byte long.

In languages that support pointers more directly, I'm doing the same thing, maybe minus the class tag in the pointed-to memory. Address is in an 8 byte type, put it on the stack and access the pointed-to struct in your new frame.

Yet I've seen interview questions about whether you're "passing by reference" or "passing a reference by value" like there's some big meaningful difference and one answer is wrong.

I don't get it. Is this just one of those nerd arguments where we're debating semantics for the sake of it?


In all of those cases, you're probably passing a reference by value, semantically. ("Probably", because you didn't specify which languages, so I'm making an educated guess.)

The usual litmus test is having two objects (or ints or whatever), write a fn `swap(a, b)` where the two are swapped after the call is over. Can't do that in Java or Go or Python or C; but you can in e.g. C++ and you sorta-can in Lisp. In C++, you'd see int& (or whatever) show up in the arguments of swap().


Ok, yeah, I saw upthread about C++ and Pascal having a more formal reference type.

But in C and Go you can pass pointers to pointers, in Java and Python objects with mutable object references..

It seems like the whole article could have been "Go does not have the C++ reference type, so it's not really in the mix when calling functions".


Yep. "Reference type" is a confused and overloaded term, so I prefer to say "pass-by-reference" when I meant int& and "pass-as-reference" (or the, in my opinion confused and confusing, pass-by-object, if that's helpful to the listener).


Just to be clear, the rules of your swap challenge are that the arguments of the function have to be ints, not allowed to be int *, right?

Also you say "two objects", but if the objects were compound, e.g. arrays, then I could write swap in C.


Yes. It's about swapping the arguments. With a struct or an array or a pointer or whatever, you're not swapping the arguments themselves; but the fact that there are so many things that do something slightly similar probably illustrates the limited utility of pass-by-reference.


answer is in the title


It is not


Doesn't the post stumble over right in the second section? "It is not possible to create a Go program where two variables share the same storage location in memory". So what? The C example didn't show that either.


You are correct that C also does not have pass-by-reference. However, the second listing is a C++ program, not a C program, and C++ very much has pass-by-reference. The C++ program demonstrates true aliases, as you can tell from the output of the printf (see comment on same line).


I have always found that is conflating the semantics of using with the method of passing. Even with/out aliases you are still passing a reference.


I don't think that's accurate. I can pass something locally declared as int to a fn that takes int& according to it's signature. It seems that's precisely what the confusion is about :)




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: