30 comments

[ 2363 ms ] story [ 963 ms ] thread
I'm not sure what the title has to do with the article at all. It's just pointing out a language gotcha/subtlety. The title might as well have been "Go is Not Javascript", "Go is not QBasic", etc.
> I'm not sure what the title has to do with the article at all.

The title has to do with a common use case of Go, referred to in the opening example, where it is a common choice to move to from Python for speed.

> It's just pointing out a language gotcha/subtlety.

The implied motivation for posting this (see the closing line) is that the author has see this bite people coming to Go from Python.

andrew's point is that's a gotcha coming from most languages. nothing python specific.
This is the kind of "gotcha" that you learn pretty quickly when working with go. The problem is that Go has created a new variable called "student" that is assigned as its value each item in students as it ranges through. Go is not going to allocate a brand new "student" variable for each iteration of the loop. That would be a waste of time and memory both during the loop, and later during GC. So your "fredPtr" given this syntax, would be more accurately named "studentPtr."

Go won't hold your hands because it's more efficient if you are explicit. If you want a "reference" to the real original, your array needs to be an array of pointers.

It's pretty simple to understand when you have worked with pointers before and understand how the memory is allocated and eventually collected (and why you don't want to reallocate every loop iteration).

Finally, nil is the default value of anything with type (star)Student, and almost nobody uses arrays in Go ([2]Student is an array, []Student is a much-more-useful slice). In general, I don't think this author has written much Go code. It's probably a good idea to get a little more experience with whatever you're complaining about before writing a blog post about it, and likely misinforming your readers.

It's pretty straightforward code when you write what you explicitly want:

    package main

    import (
      "fmt"
    )

    type Student struct {
      name string
    }

    func main() {
      students := []*Student{{"fred"}, {"karen"}}
      var fredPtr *Student

      for _, student := range students {
        if student.name == "fred" {
          fredPtr = student
        }
      }

      fmt.Println(fredPtr.name)
    }
Ironically, your code looks much more like Python than the author's. "Simple is better than complex" at play, I suppose. If I were to sit down and start writing Go, the idea to use equality would come long before I bothered with pointer notation, especially for such a simple task.
(comment deleted)
This has nothing to do with Python. It's just a "gotcha" of Go.
I believe he's talking about what he should teach to students, and his point is that Python is more intuitive than Go for newcomers.

I personally don't really care how easy it is to write a "hello world" in Go, nor Python. My day job involves big projects, so I'm pretty fine with "hello world" taking 10k lines as long as it will scale slower with complexity than other language. The same argument about learning curve -- I would be happy with a language that requires couple of years of learning to even start, as long as it takes less time in the following 20 years I'm going to be using it.

I believe he's talking about what he should teach to students

What gives you that impression?

Huh, I was reading it fast, and didn't realize that word "students" in the text meant a variable name, not his students. My bad :)
Heh yeah and I ended up re-reading it looking for some indication the writer was an instructor that I'd missed and didn't notice 'students' at all.
I don't get that impression at all from the article. I think he's just trying to warn others coming to Go from Python about a particular language quirk. Nothing about the intuitiveness of Go or Python at all
been a while since i've done pointer stuff, isn't that how it works in c? go seems de-evolved to me.
C has no concept of the kind of for loop used in the article, so there is no "how it works" in C.
While C has no for-in loop, the natural translation of that to a C style for loop and the C address-of operator would work the same way; the key thing is that student is a variable defined outside the loop whose value is modified each iteration, not a fresh variable allocated with each loop iteration.
If by "de-evolved" you mean 'not as high level', then I would agree: Go is indeed a lower-level language than Python.

— Go is basically a better (more evolved?) C.

I love Python. But honestly speaking, the point raised in this article is fairly trivial. I would not, not learn Go just because there are small differences in the way reference / pointers work. I do not see this as an inherent shortcoming of Go.

Both languages have their strengths. When you pick a language for an application, it is your responsibility to use it properly. Otherwise you will make mistakes and it won't be the fault of the language.

(comment deleted)
> But honestly speaking, the point raised in this article is fairly trivial. I would not, not learn Go just because there are small differences in the way reference / pointers work.

Nothing in the article suggests not learning Go because of this behavior; the article is warning about a behavior developers coming to Go from Python are, in the author's experience, often bitten by, so that other people taking the same path can avoid making the same mistake.

> When you pick a language for an application, it is your responsibility to use it properly.

Yes, and this article aims to help people coming to Go from Python to do that by pointing out a common pitfall.

Here's a more idiomatic translation of the Python code to Go, and there's no surprise to how the code behaves:

https://play.golang.org/p/rpqvVDR9v8

I was scratching my head looking at the authors code wondering why he didn't just write it exactly how you did.

Although technically he was trying to get a pointer/reference to the student in the array, not a copy of its value

I don't know Python or Go, but both examples seem _wrong_.

Python: How is `Fred` available to be printed in the Python example, is not it out of scope?

Go: Why use `var fredPtr *Student`? What's the point? Can not use `var fredPtr Student` and the set `fredPtr = student` in the loop?

(comment deleted)

  fredPtr = nil // This line is superfluous.
In Go, declarations always initialise variables to their zero value, which for all pointers is nil.

(Perhaps worthy of a follow up article: "Go is Not C/C++"? ;)

I think you can put this more generally as "syntax is not semantics." Go takes some syntactic cues from python (but who doesn't these days?) but its semantics can be pretty different. I think it's pretty common for new programmers especially to get caught up in the syntax of languages without giving enough attention to their semantics, which causes the sort of problems the author highlights here.
> I think you can put this more generally as "syntax is not semantics." Go takes some syntactic cues from python (but who doesn't these days?) but its semantics can be pretty different.

Except the difference is not in similar syntax with different semantics; since it's the application of the & operator, which has no close parallel in Python, that is the source of the surprise.

for a no-goer: will this copy by value? ``` var fred Student for _, student := range students { if student.name == "fred" { fred = student } } ```
Yes. A copy into type `Student` will copy each field, whereas `*Student` only takes the address. (Which would not be a problem if the loop didn't reuse the memory slot for the loop variable.)

Also, indent with two spaces to get code formatting. HN formatting is (sadly) not Markdown.

Alternative options of solving this issue include `break`ing when the right option is found (so the loop variable doesn't get overwritten again) or moving everything into a function:

  func findByName(students []*Student, name string) *Student {
    for _, s := range students {
      if s.name == name {
        return s
      }
    }
    return nil
  }
Both options have the added advantage of stopping iteration as soon as the result is found.