Hacker News new | past | comments | ask | show | jobs | submit login
A Rust Contributor Tries Their Hand at Go (polyglotweekly.com)
415 points by Manishearth on April 28, 2015 | hide | past | favorite | 213 comments



I really enjoyed this review. You can tell the author spent some time trying to spot the good and bad things about Go. Too many Go reviews turn out to be: Watch me trying to use Go as a functional programming language and fail miserably because it lacks generics.

Anyway, just like the author I found myself fighting the forced directory structure at first, but it's something I started to like about Go: Go doesn't come with a complicated package management. All packages lie in a directory and you can edit everything with normal shell commands. This keeps things refreshingly simple.


>Watch me trying to use Go as a functional programming language and fail miserably because it lacks generics.

Go's lack of generics and its incapacity for functional programming are mostly unrelated.


Can you add more color here? What is the separation between generics and functional programming?

As an example in favor of their coupling it is very hard to write generalized functions for list operations without generics (without duplicating code). It is especially hard if you want to maintain type safety and don't resort to type erasure (interface{}).


>What is the separation between generics and functional programming?

What is the connection? There are tons of functional languages (Tulip, Racket, some Lisps) that are untyped or optionally typed.

Strong types are awesome, but you don't need them for FP. Go isn't good at powerful typing or functional programming.


You can do FP in Go — just not in a type-safe way. OTOH, chances are you'll still get more type safety than doing FP in Common Lisp.


Well, regardless of exactly what static typing facilities CL has, at least without static type info you wouldn't need all those ugly casts.


> OTOH, chances are you'll still get more type safety than doing FP in Common Lisp.

Maybe that was the case in the past? Now there's Shen, so now you can get a lot more type safety in Common Lisp than in Go.


Definitely Shen, Typed Scheme, and Clojure give you much more type safety than "plain" Lisp.

My closest encounters with Lisp is writing elisp, which lacks all these amenities. Thus my comment.


It's not entirely that bad in CL:

(locally (declare (optimize (safety 3)))

  (defun list-int-p (list)
    (every #'integerp list))

  (deftype list-int ()
    `(satisfies list-int-p))
  
  (defun xxx (list)
    (declare (type list-int list))
    list))
It's still runtime check, but better than nothing.


Runtime checks can be implemented anywhere — I did similar things for Python and JavaScript, as many others here did, I suppose.

It's better than nothing, but it still bombs in prod, instead of giving you a static analysis error.

The nice thing about static types being a core part of the language (ML, Haskell, C#,...) is that you have concise syntax and type inference at all the required spots. Otherwise (Clojure) you have to be pretty verbose, as if you were writing Java.


In all fairness the dynamic nature of LISP is an advantage for the people using it. Yes you can talk about having bombs in production that would otherwise have been caught by the compiler, but unless you have a really expressive type system, like Haskell, then it's not going to help you much. If you're into static type safety, than Go is a really poor choice.

And this is one of my problems with it. Either a language is statically type safe, or it is dynamic. You can then fully embrace the mentality of those languages.

However Go is in the league of languages created to be popular with the average Joe and just like Java before it, it's neither here nor there.


I very much agree with you.

A zip code is a string even though it looks like a number.

Likewise C/Java/Go "types" are tags even though they look like types. Real types are numbers that obey type algebra, which C/Java/Go have no idea exists.

The Algebra of Algebraic Data Types: https://www.youtube.com/watch?v=YScIPA8RbVE


In regards to Common Lisp, depending on which implementation you use you may get some sort of type checking at compile time.

For example, if you save the following code[1] into a file and try to load it into SBCL, it will give a compile time error:

    (ql:quickload 'defstar)
    
    (use-package 'defstar)
    
    (declaim (optimize (debug 3) (speed 0) (safety 3)))
    
    (defun* factorial ((x (integer 0 *))) ;; x is an integer from 0 up to +inf
      (if (zerop x)
          1
        (* x (factorial (1- x)))))
    
    (defun main ()
      (factorial "foo")  ;; Compile time error: "foo" conflicts with its assert type UNSIGNED-BTYE
      (factorial -1)     ;; Compile time error: -1 conflicts with its assert type UNSIGNED-BTYE
      (factorial 1.0)    ;; Compile time error: 1.0 conflicts with its assert type UNSIGNED-BTYE
      (factorial 0))     ;; Works

And when you load the file into SBCL you get the following error message (assuming you have Quicklisp installed):

    $ sbcl --load compile-time-safety.lisp 
    This is SBCL 1.2.7, an implementation of ANSI Common Lisp.
    More information about SBCL is available at <http://www.sbcl.org/>.
    
    SBCL is free software, provided as is, with absolutely no warranty.
    It is mostly in the public domain; some portions are provided under
    BSD-style licenses.  See the CREDITS and COPYING files in the
    distribution for more information.
    To load "defstar":
      Load 1 ASDF system:
        defstar
    ; Loading "defstar"
    
    
    ; file: /home/rol/src/lisp/compile-time-safety.lisp
    ; in: DEFUN MAIN
    ;     (FACTORIAL "foo")
    ; 
    ; caught WARNING:
    ;   Constant "foo" conflicts with its asserted type UNSIGNED-BYTE.
    ;   See also:
    ;     The SBCL Manual, Node "Handling of Types"
    ; 
    ; compilation unit finished
    ;   caught 1 WARNING condition

It's not as nice as Haskell's type system, but it's something :)

The main problem with this sort of compile-time checking is that it's implementation dependent, so some Common Lisp implementation may not provide it. You can configure the defstar library that I used on this code so that it automatically adds calls to "(check-type ...)". But then you are back at doing run-time checking of types.

--

[1] - The defstar library can be found on Quicklisp or here: https://bitbucket.org/eeeickythump/defstar/

It's pretty much just a nicer way to write all of the "(declaim ...)" and "(declare ...)" needed to provide the most information to the Common Lisp type system.


If you want to represent effects as pure value + interpreter (e.g. IO from Haskell) then you pretty much need generics. To my mind avoiding side effects is a central part of "functional programming", but the term means different things to different people.


Except for the tiny fact that you don't get higher-kinded types, which is huge for functional programming.


HKTs aren't even remotely necessary for basic functional programming.

HKTs are very useful for e.g. typeclasses (used for Monad, Functor, etc. definitions) but you don't need them. Strictly speaking, you don't even need types at all (even though they're great).


That's totally right which is why I never said anything to the contrary.

You're crazy if you do FP without HKT though.


Higher-kinded types can definitely be overused. One has to be careful with higher-order anything, since it complicates reasoning about programs. (First-order is simple if you can get away with it)


Many statically typed FP languages don't have higher kinded types, or haven't had them for very long. They are kind of new.


Such as?


F# for sure. I'm only sure that Haskell and Scala have them, I'm not sure about SML, OCAML, and the other statically typed FP languages (definitely none of the simple statically typed OO languages have them).


Note that Rust doesn't have proper HKTs yet, though I believe we'll be getting them eventually.


You're exactly right. It's sad that a lot of people in the community cannot keep an open mind when they evaluate a technology.


Which part of his review was about functional programming in particular? Generics has nothing to do with functional programming at all. I think it would be worth to clarify what does functional programming mean to you.


zuzun said:

> Too many Go reviews turn out to be: Watch me trying to use Go as a functional programming language and fail miserably because it lacks generics.

You replied:

> Which part of his review was about functional programming in particular?

I think you totally missed zuzun's point. The point was not that this review was about functional programming, the point was that this review was not about functional programming.


Yeah like all of the other guys in the thread. Everybody missed. I understand now, my bad of getting involved in this low class debate that has nothing factual or valuable.


There is one minor syntactical convenience available to make the message example slightly more concise: switch msg := msg.(type) would make msg a message of the specific type inside each case. (So msg would be a DrawLine between 'case DrawLine:' and the next case.) Author might have just wanted different names in the different cases, which is a perfectly defensible reason for doing it the way he did instead.

He says "Go seems to advocate runtime over compile time checking." I think it's less that folks prefer run-time checks on principle than that they simply don't yet have in hand great ways to strengthen compile-time safety while meeting their other goals (meeting backcompat promises, not complicating spec/impl too much, other work like shorter GC pauses or new platform support). As an example of a team member not being a fan of all the runtime checks, bradfitz complains about the occasional gross interface{} here: http://talks.golang.org/2014/gocon-tokyo.slide#50 . And there is active work from the team and community on static checking, though most of that right now is in the form of heuristic checks in external tools, e.g., go vet and errcheck.

All in I love this--I tire a bit of posts that are like "I wrote a toy program [or not even that]; here's Why Everyone Else Is Wrong" (because anyone could give an opinion that informed given a day or two, and hot takes are getting old). But it's cool to see someone who, by virtue of working on Rust (and seeing it evolve) has unusual perspective on tradeoffs and seems to have written, you know, pretty thoughtfully and maturely about them.


That's pretty sweet, I'll remember that trick. The `range` trick was another one that took me some time to realize, there are lots of neat sugary things in Go like this :)

> I think it's less that folks prefer run-time checks on principle than that they simply don't yet have in hand great ways to strengthen compile-time safety while meeting their other goals

Agreed, that sentence was a bit unclear from me. I did mean that they're okay with losing compile time safety over other benefits.

> All in I love this--I tire a bit of posts that are like "I wrote a toy program [or not even that]; here's Why Everyone Else Is Wrong" (because anyone could give an opinion that informed given a day or two, and hot takes are getting old). But it's cool to see someone who, by virtue of working on Rust (and seeing it evolve) has unusual perspective on tradeoffs and seems to have written, you know, pretty thoughtfully and maturely about them.

Aww, thanks :D


I've written a fair amount of Go and his view is pretty consistent with my experience. Go has a good culture of doing things the simplest correct way and only that way. Rust is really interesting because of the stronger compile-time assurances and lighter footprint (looks like it'll be a good candidate for embedding into mobile apps), I'm learning it in the hopes that I'll be able to use it as an alternative to C for portable library code.


Sorry for asking off topic question. What is good resource you suggest for understanding go memory management. For someone I am unable to understand from golang docs


The best place to start, if you haven't read it, is the documentation here: https://golang.org/ref/mem

For more detailed information on the garbage collector improvements and roadmap for 1.5 and beyond, this document is the Roadmap and discusses improvements being made including a maximum of 10 ms STW out of every 50 ms. https://docs.google.com/document/d/16Y4IsnNRCN43Mx0NZc5YXZLo...

edit: fixed incorrect link


In addition you can get some insight for the "why" questions by reading through their mailing lists (dev and nuts).


The nice thing about Go is that you rarely have to think about memory management. Can you access the object? Then it exists! (even across threads)

You should use locks for thread safety though.

That being said from a Rust background I like to be able to know what's going on at the lower level and Go makes that harder, but that's not a major issue whilst programming.


FWIW in Rust "Can you access the object? Then it exists!" is true too, however you have to wait for the compiler to reach a certain point to know that you indeed were allowed to access it[1], whereas in Go you can figure this out by looking at the code :)

[1]Or internalize the borrow checker, which is something which eventually, automatically happens to all Rust programmers AFAICT. It actually spills out into other programming languages too so you get a nice involuntary mental tool to ensure safety in C++ :)


For thread safety, you only want locks if your variable is shared. And even then consider using "sync.atomic" compare and swap operations instead.

It's often possible to avoid sharing by passing discrete data around via channels. As a general rule, if it only has one owner, it's thread safe.


> Having interfaces be autoimplemented [...] it's just not what I'm used to and it restricts me from writing certain types of code without using dummy methods like so to get static type safety.

There's a Go idiom for that. If you have

  type Doer interface {
    Do()
  }
and a

  type thing struct {
  }
and you want to make sure that `thing`s implement `Doer`, just add this line:

  var _ Doer = thing{}
_ is a special value that can be redefined at will, so the pattern can be used as many times as needed. From there, the meaning should be straightforward: _ is a `thing` that implements `Doer`, so the compiler will check it. You get to keep implicit interfaces, that you can make explicit as you need.

Apart from that, this post is really great; as someone "used" to Go and not at all to Rust this kind of impressions is always enlightening.


There's still no solution for the cowboy/shape problem (where you want to have a single object that can implement Cowboy::draw() and Shape::draw()) though, presumably.


You can use object composition: http://play.golang.org/p/jyS38luYjM

Then make a Draw() method on the single object default to what you expect. The simple inline explanation is:

    type SingleObject struct {
        Cowboy
        Shape
    }
Access is as follows to be unambiguous:

     Woody.Cowboy.Draw() 
     Woody.Shape.Draw()


The problem is that you can't differentiate SingleObject from CowboyObject and ShapeObject.

They all are the same interface and can be passed into each other's methods, leading to a logical error.


The solution is to use more specific names. E.g. Cowboy.DrawGun(), Shape.DrawTo(canvas)

Yes, sometimes this isn't an option when you want to satisfy the interface for two different libraries, but I've not seen this occur very often. More likely you're in control of one of the interfaces; so rename your method to be more descriptive.


> Yes, sometimes this isn't an option when you want to satisfy the interface for two different libraries

This is assumed in the description of the problem. Go has no solution for it. (Java doesn't either, but this is one of the things that C# got right over Java; it's unfortunate that Go has the same issue.)


You skipped the important part of my statement:

> , but I've not seen this occur very often.

Sure, it's a problem in theory. Whether it's a problem in practice is a different question. I'd be interested to see examples of this problem in the wild.



Which one of these links should I click to see someone having an actual practical problem based on this issue? Nobody is debating whether people notice the issue exists.


The very first one has a comment:

> I encountered such a case where a legacy "Address" class implemented Person and Firm interfaces that had a getName() method simply returning a String from the data model. A new business requirement specified that the the Person.getName() return a String formatted as "Surname, Given names". After much discussion, the data was re-formated in the database instead. – belwood

Further down someone hit a similar issue with the getString() method in some of the Android frameworks.


These examples seem to exemplify java's tendency towards design astronautics and the getter-and-setter-for-every-field pattern more than demonstrate the problem we're discussing here; I would find it very strange to see a `GetName() string` method on a go interface.

Even so, this problem could still have been fixed by just renaming the method to be more descriptive because they were still designing it. The only time this would be a problem in Go is if you had two unfortunately and oddly named methods in two libraries where you can't control either.

Edit: And to take it a step further, I would say that since interfaces are Go's only resource for representing generic behavior, go developers are more mindful of the methods they add to interfaces, in both number and descriptiveness.


> I would find it very strange to see a `GetName() string` method on a go interface.

How else do you write a function that can take multiple types of structs, all of which have a Name field, and select that field? (Other than reflection.)

I mean, there are a bunch of Go packages that have types with a method named precisely that: https://www.google.com/search?q=golang+"getname"

> Even so, this problem could still have been fixed by just renaming the method to be more descriptive because they were still designing it. The only time this would be a problem in Go is if you had two unfortunately and oddly named methods in two libraries where you can't control either.

Like in the Android case mentioned above? The Android system frameworks required a getString() method, while the MVVM library the commenter was using required an incompatible getString() method. Neither could be changed without a lot of headache.


Funny, the first link in your google search is slides for a presentation called "Go for Javaneros". The presentation takes a java program, finds a problem with it and fixes the problem in both java and Go. Go's solution starts with a `Name() string` method, but then he drops the method completely in favor of a public Name field instead. Other links in that google search are clear examples of a naive java-to-go translation with half a dozen `GetX() X` methods.

> How else do you write a function that can take multiple types of structs, all of which have a Name field, and select that field?

That presentation that you linked to says to use struct embedding aka composition. And if you don't want to do that then I'd say you're "probably" framing the problem wrong, but we really need a more concrete example to discuss this further otherwise we'll just be talking past each other.


This by the way is a theme with Golang: Golang's answer to insufficiency in its ability to model problems is virtually always a pragmatic reframing of the problem without all the modeling.

You can see this through the lens of i.e. Peter Norvig writing a Sudoku solver vs. that object modeling guy, or you can look at it through the lens of "the language should capture problems as safely and completely as possible".

You can take either perspective too far.

When Pike suggests implementing generics with "go generate", I feel like he's taking it too far. Here, I think maybe we're too far the other direction.


> Here, I think maybe we're too far the other direction.

We have at least three examples of this being an actual problem in practice that could not be worked around. Two in Java, and one in Golang itself. I don't know how you can come to the conclusion you did.

In particular, "struct composition" does not solve the problem any more than inheritance would in Java. This comes up in cases in which the structs in question have separate declarations and you don't want to force all of your method's callers to inherit from your class/embed your struct just to call your function.


I think the objection is that you rule out workarounds that involve rethinking decisions about what should be a struct, what should be an interface, and what things should be named.


Sorry, if your position is "Golang doesn't use getters to abstract over structure fields", that's just not true. Here are some examples:

- https://golang.org/pkg/database/sql/driver/#Rows -- Columns is a getter

- https://golang.org/pkg/database/sql/driver/#Stmt -- NumInput is a getter

- https://golang.org/pkg/image/#Image -- ColorModel and Bounds are getters

- https://golang.org/pkg/reflect/#Type -- Align, FieldAlign, NumMethod, Name, PkgPath, Size, Kind, Bits, ChanDir, IsVariadic, Elem, Key, Len, NumField, NumIn, NumOut are all getters

And so on.

The reflect package could, for example, have the Type-returning methods return a struct using structure embedding to handle most of those fields. But they didn't write the API that way, because it would be annoying to do so. So they used interfaces with getters instead. This isn't Java "astronaut" design—it's just how you program in a language with interfaces.

Look at the implementation of PkgPath() for example: https://golang.org/src/reflect/type.go#L446

There's only one implementation of PkgPath(), and it does nothing but address a struct field. The functions could expose uncommonType directly, but they didn't. That's because getters are a common design pattern in Go.


It is absolutely not idiomatic Golang code to use getters and setters rather than struct fields, as I am painfully aware after a day wasted wrestling with net/http and httprouter. Golang strongly prefers simple struct fields.

You correctly observe than in order to conform to a Golang interface, struct fields have to be exposed through setters and getters. But all those examples are interfaces, not structs.

The gap between your summary here and the Golang idiom is that Golang doesn't want you to make everything an interface. Generally, Golang interfaces seem to fit one of two molds:

* Interfaces that any systems programmer could predict needing ahead of time, like io.Reader and io.Writer.

* Interfaces that are born "bottom up" to allow for a very specific bit of code reuse.

Golang's standard library does not have a lot of interfaces that are conceived of as hooks for future functionality and opportunities for decoupling, which is a stark contrast from Java libraries, which do.


Those examples I gave all could have been structs, but weren't. Image.Bounds() and Type.IsVariadic() could have been plain struct fields, for instance. Grep through the Go source code for "func Bounds()" and observe that every single implementation of that interface is "return p.Rect" (well, except Uniform, but that one could have been a field too). Sure, it'd require some contortions to factor out your code so that interfaces work, but that's precisely my point: getters are a natural way to program with interfaces in every language.

Does Java tend to use getters unnecessarily where fields could have sufficed? Absolutely. Go does not, and that's a great thing about the language. But I think that's irrelevant to the point at hand about explicit interface names. In none of the cases where the name collision problem was an issue would moving things to fields have helped. For Next() in Golang, it was actually a method with behavior that was causing the problem, not a simple getter. For getName() in Java, the problem was that two different libraries that needed to work abstractly with a user object needed different "views" of the same field to work properly, and there was no way to disambiguate—observe that with fields, you'd have the same problem! Likewise for the Android library. In these cases, you really want explicit interfaces.

The right objection, in my mind, is not "just use fields" but is instead "use the adapter pattern". That is, acknowledge that yes, this is a problem in practice, but when it arises you can just make a separate type wrapping your type and implement different methods on that wrapper. That is indeed a solution that works around the issue. The question from a language design POV is then whether the benefits of structural interfaces outweigh the downsides, one of which is requiring programmers to write adapters to solve name conflicts. Reasonable people can differ on that, and I think it's obvious I disagree with Rob Pike here. But presenting fields as an adequate substitute for getters is inadequate (since you really do need getters), unappealing (since getters are great, which is why Go uses them) and pretty much totally tangential to the problem.


The thing is, implicit interfaces give you so much freedom and make it so easy to keep your code decoupled, that the very rare times that you hit something like this Next() problem, you just work around it and accept it as a cost for all the other benefits it brings.

For the record, in Juju we have hundreds of interfaces and hundreds of thousands of lines of code, and AFAIK have never had this be a problem.

For the given Next problem, you can easily make a wrapper type that simply masks the underlying Next() method with its own implementation.

    type MyType struct {}
    func (m *MyType) Next(i int) {
        // do base next stuff
    }

    type ForSql struct {
        *MyType
    }
    func (f ForSql) Next() bool {
        // do sql-next stuff
    }
and then using this type is trivial in-code:

    mt := &MyType{}
    doSomeSqlThing(ForSql{mt})
Note that this new type retains all the methods of the original type, so it still implements all the interfaces it used to (unless they rely on the old Next signature).

Compare this to the benefits of implicit interfaces (decoupled code, easy composition, using third party code as your own interface type without needing a wrapper type)... hopefully it's pretty obvious this is a large net positive.


Real world example I hit a few weeks ago:

I implemented a DB driver that had its own Next() method. Later I wanted to implement the SQL package interface. That interface contains Next() (http://golang.org/src/database/sql/sql.go?s=40568:40595#L157...).

Choices - break backwards compatibility and change the interface I control or convolute the type definitions to create a type specific to the database/sql interface.

This limitation in combination with methods only being distinguished by name, not by parameters, turns out to be very annoying -- especially as interfaces profliferate.


Just a copy and paste from the solution I posted elsewhere... this really shouldn't be too hard to work around:

You can easily make a wrapper type that simply masks the underlying Next() method with its own implementation.

    type MyType struct {}
    func (m *MyType) Next(i int) {
        // do base next stuff
    }

    type ForSql struct {
        *MyType
    }
    func (f ForSql) Next() bool {
        // do sql-next stuff
    }
and then using this type is trivial in-code:

    mt := &MyType{}
    doSomeSqlThing(ForSql{mt})
Note that this new type retains all the methods of the original type, so it still implements all the interfaces it used to (unless they rely on the old Next signature).


Thanks for the example. Out of curiosity, what was the signature of your Next method?

> I implemented a DB driver that had its own Next() method. Later I wanted to implement the SQL package interface.

(Did you mean the database/sql/driver driver.Rows interface[1]? sql.Rows is a struct not an interface.)

Just to be clear, you implemented a DB driver and then went to satisfy the driver interface? And this was after you already published it and would break backwards compatibility?

> especially as interfaces proliferate.

True. There will only be more interfaces over time. We'll want to keep them as small and descriptive as possible if we want the ecosystem to be sustainable.

[1]: https://golang.org/pkg/database/sql/driver/#Rows


Second option (separate interface) is obvious choice with duck-typing interfaces.

edit: just to clarify: I don't like the idea "write more descriptive (over-verbose) method names" as solution for this problem.


> Second option (separate interface) is obvious choice with duck-typing interfaces.

And this is called the "adapter pattern" in Java. It's also very annoying to have to use it when you hit it.


It's not super elegant, but if you need to satisfy the same interface in two different ways, you can do it with a helper that gives a particular view into your class.

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

Edit: another variation on the same idea: https://play.golang.org/p/FqSFpMKQuv


oo, that's an interesting pattern :)


This is cool. Do you know any blog posts or documentation or threads where this is discussed in more detail?


I came across this while reading bradfitz' camlistore code; I didn't see any extensive documentation or post about that.

There is a paragraph in the faq (https://golang.org/doc/faq#guarantee_satisfies_interface) and one in "Effective Go" (https://golang.org/doc/effective_go.html#blank_implements), but I couldn't find much more than that.


> Visibility (public/private) is done via the capitalization of the field, method, or type name. This sort of restriction doesn't hinder usability, but it's quite annoying.

While the capitalization of identifiers may be surprising, in practice it is one of the best features of go in my experience because no additional context is needed to know whether an identifier is exported. In rust, as far as I can tell, it is not possible without referring to the declaration.

As far as globally changing whether an identifier is exported, it is easy with the gorename tool, no search and replace necessary. Gorename also updates any references to the identifier in other packages in $GOPATH. https://godoc.org/golang.org/x/tools/cmd/gorename


no additional context is needed to know whether an identifier is exported

Isn't this the same as the argument for Hungarian Notation? And isn't it a bit of a slippery slope? Why not add an 'i' for integers, so that no additional context is needed to know whether a variable is an integer. Etc.

There is a lot of information about a particular identifier that it would be nice to know at any given time, and in my opinion it's arbitrary to pick just one and make it a language rule. A much better solution is for to use IDEs or text editor plugins to visually show (using color, formatting, bits of UI) that information in a user-configurable and context-dependent way.


In defence of hungarian notation, the way Joel tells it makes a lot of sense [0]:

"Simonyi’s original concept for Hungarian notation was called, inside Microsoft, Apps Hungarian, because it was used in the Applications Division, to wit, Word and Excel. In Excel’s source code you see a lot of rw and col and when you see those you know that they refer to rows and columns. Yep, they’re both integers, but it never makes sense to assign between them. In Word, I'm told, you see a lot of xl and xw, where xl means “horizontal coordinates relative to the layout” and xw means “horizontal coordinates relative to the window.” Both ints. Not interchangeable. In both apps you see a lot of cb meaning “count of bytes.” Yep, it’s an int again, but you know so much more about it just by looking at the variable name. It’s a count of bytes: a buffer size. And if you see xl = cb, well, blow the Bad Code Whistle, that is obviously wrong code, because even though xl and cb are both integers, it’s completely crazy to set a horizontal offset in pixels to a count of bytes."

It's actually a practice I now use myself in certain contexts.

[0] http://www.joelonsoftware.com/articles/Wrong.html


Aside: Instead of hungarian notation in Rust we do this[0]. Free compile time unit safety :)

[0]: https://blog.mozilla.org/research/2014/06/23/static-checking...


Note, you can do this easily in Go, too:

    type Kelvin int
    type Celcius int

    func SetReactorTemp(temp Celcius) {}

    func GetOperatingTemp() Kelvin {}
 
    temp := GetOperatingTemp()
    SetReactorTemp(temp) // compile error


You can, but it quickly becomes infuriating in a language without generics, because you have to repeat any data structure that you want to potentially contain different kinds of temperatures.


I won't get into it on generics other than to say, in my 2 years of full time development in Go, not having them has not been a big deal.... and no I don't use type unsafe code everywhere either.


This is so exactly the argument for a decent type system that seeing it used to argue for a naming convention is downright surreal.


Yes, a good type system would help with these issues, but that's not the only way to crack this egg. I mostly work in Python, so it's not an option, but even if it were, using types for this stuff is probably a pretty heavy solution in some cases. Obviously, that depends on your type system, but for most of them having to define new types for different types of array indexes would bre really clumsy.

As soon as you open the types basket you have quite a lot of overhead that you didn't have before (it's a tradeoff, as ever). Not only that, but types won't help you on the readability front - not without an IDE. Spolskey's argument is that a naming convention can make the code look obviously wrong, and there's nothing wrong with that.

Hungarian notation has its place. I've used it before, and I'll do it again. Used correctly it can make the code safer and clearer. Types give you that too, but a naming convention is as simple as it gets, and it's free!


Why do you need the code to look obviously wrong when you have a compiler capable of telling you that it's wrong? It doesn't require a full-blown IDE to get fast feedback; I use a fairly lightweight vim config that gives me type-checking every time I save a file.

I also disagree about overhead. Defining new types is an O(1) cost, and decent modern languages should have type inference so that there isn't a recurring annotation cost either. Even accounting for the cost, if the benefit is 10 units and the cost is 1 unit, saying only that “it's a tradeoff” is a bit absurd. When there are tradeoffs, engineers don't throw up their hands and flip a coin, they calculate the costs and benefits and use the best solution. Types easily win in that calculation.

I don't disagree with using notation to fill the gaps when you must work in a weaker language, but we shouldn't pretend that it isn't a weakness.


I think maybe my comment sounded like I was advocating notation over types. I'm not, and I agree with everything you're saying.

I stand by my assertion that there are situations where a little notation is a good thing.

A better argument for notation is the readability of the code (which I place a high value on). That's a place where the types don't help as much, but the notation does.


That post was what put me on the road to functional programming. I started using the Checkers Framework after reading it, and then graduated to Scala.


This is one of the cases where I like languages that allow extendable types.

Being able to declare that a variable is in radians, for example. (Bonus points if it can auto-convert.)


Go can do this easily:

    type Radians float
    type Degrees float

    func (r Radians) ToDegrees() Degrees {
        // maths here
    }


That's not what I mean by autoconverting.

I meant as in quite literally being able to use something of type Radians in something that expects Degrees and it "just works", and vice versa.

I dislike Go's overuse of "explicit being better than implicit", and this is no exception. It's supposedly to prevent bugs, but it adds so much more verbosity that half the time you're adding more bugs by the additional verbosity than you're fixing via the reduction of magic.


That's not auto convert. Scala implicit is auto convert.


Auto converting is an anti pattern. Explicit is better than implicit. Auto conversion is how mistakes get made. This is why modern languages don't let you implicitly convert from a string to an int, for example.


Sounds interresting! Could you provide a link? Can't seem to find anything relevant.


For a quick example, take C:

    typedef rad_f float;
    typedef degree_f float;
Although in C there's no way that I know of to do implicit conversion between the two.

The closest thing I've found to what I mean is C#'s implicit cast system:

http://nathan.ca/2014/02/type-rich-programming/


Thanks for the response. I was more thinking about languages which have mechanisms for this though. What did you mean by extendable types?


It's not the same.

In Hungarian Notation, the type is enforced by a declaration, but it's documented by the prefix.

In go, the enforcement and documentation are one and the same.


The Hungarian notation is informal and not enforced by compilers. You can easily do

>> bool iMyInt;

It's obviously wrong, but the compiler will happily compile it. When you declare the following in go:

>> type NotExported struct {}

You still know it's exported, no matter what BS the programmer writes.


I really like the idea of capitalization selecting public and private scope. However, how do they use protected scope?


There is no protected. There's no inheritance anyway.


Note that everything in the same package can access all the private stuff... so you do have some flexibility. You can write two types that reference each other's private data/functionality... they just need to be in the same package.


pretty even-handed.

> The other day I had to make a bunch of fields public to satisfy Go's encoding package, and code needed to be updated everywhere with a manual find/replace (the same string was used in other contexts too so it couldn't be done automatically)

gofmt has syntax-level replacements. So you could do:

`gofmt -w -r 'unexportedName -> ExportedName' *.go`

or something similar, and it would do the correct thing, ignoring the string matches where it appears in other contexts.


Thanks!

I wish I'd known this a week ago, but I'll keep it in mind for the next time I'm poking at Go :)


I use gorename http://gopkgs.com/doc/pkg/gopkgs.com/gc/go.tools/cmd/gorenam...

and it works well with vim-go integration on vim for example


We should really think about getting something like this in rust/rustfmt. Syntax-level replacements sound pretty awesome.


This is why we need an IDE or Neovim + some epic plugins. Who's working on one?


As far as vim goes, https://github.com/fatih/vim-go is the best all-in-one plugin. Really a pleasure to use and easy to install. Should work with neovim too.


I've had a pull request in to add support for GoRename, to the GoSublime plugin for SublimeText. Works great.

https://github.com/DisposaBoy/GoSublime/pull/569


well neovim might not be ready to go, but vim-go is a very nice suite of tools for vim.



there is liteide already which does exactly that (right click on a symbol refactor->rename symbol under cursor)


> Despite the performance costs, having a GC at your disposal after using Rust for very long is quite liberating. For a while my internalized borrow checker would throw red flags on me tossing around data indiscriminately, but I learned to ignore it as far as Go code goes. I was able to quickly share state via pointers without worrying about safety, which was quite useful.

I haven't gotten very far into the article yet, but this paragraph sent up a huge red flag for me. GC does not make the code safe. All it does is ensure that values don't get destroyed while they're still referenced. But you still run the risk of data races. If you're writing single-threaded code then this is fine (because single-threaded code doesn't have data-races), but the program being written here is a concurrent program.

Obviously you can write safe programs in this fashion. But you still have to think about safety. The difference is the compiler doesn't verify safety for you, which means you can write code fast without worrying about the compiler yelling at you, but your program may have race conditions in it.

Anecdotally, a few years ago when I was active with Go, a program of mine would segfault about once a month. It was a command-line utility that I ran maybe a few times a day. The segfault was so sporadic that I could never figure out what was going on. Then Go 1.1 introduced the data race detector. Using that, I was able to discover that the Go runtime was actually spinning up two goroutines when I thought it was only using one (servicing the stdout and stderr of a child process), and the one buffer object I was writing to turned out to be written to from two separate threads. This usually worked, but on that rare occasion it would lose the race and overrun the buffer. This is a bug that plagued me for half a year, and I'm really glad that the Go team created that data race detector because I never would have figured out what was going on without it. But it's a bug that simply cannot happen with Rust.


I was focusing on single threaded safety really. I do know that data races can't be prevented by a GC, but having to only worry about data races and not the regular memory safety bugs was quite nice.

<3 Go's race detector (also the deadlock debugging support). Used it quite often though I didn't hit it much.

Agreed, none of this is a problem with Rust :)


> Using that, I was able to discover that the Go runtime was actually spinning up two goroutines when I thought it was only using one (servicing the stdout and stderr of a child process), and the one buffer object I was writing to turned out to be written to from two separate threads

Wait, does it mean that go makes your code running in more threads without you knowing? When can that happen? Sounds scary if true.


It means that there's nothing in the API that tells you whether the function might spawn a goroutine. It has to document it.

I don't remember the precise details anymore, but my recollection is I was spawning a child process and passing Writers to use for stdout and stderr (looking at os.exec, I don't immediately see anything that matches my recollection, so I'm wondering if maybe I was using a different package). I expected that it would internally do something like select over stdout and stderr in one goroutine, therefore making it safe for me to pass the same buffer object for both (because I wanted to combine writes). But instead it was spawning 2 goroutines, not 1. And there was no evidence in the API that what I was doing was wrong, the only way to know was for the data race detector to flag it.


Not bad, at least a fair look!

One thing that immediately set off my radar (as a Go programmer) is the paintingLoop switch on type.

Yes, sometimes a switch on type is necessary. However, this is not one of those cases.

You want to have a Triangle and Line with a Draw() method and a "Drawable" interface... and then just call into it. The signature of the loop in question then is paintingLoop(ch chan Drawable).

An abuse of naming, but a Quit.Draw() could actually quit, for example. This is just offhand though.

In short, good article, but the Go examples are written with a Rust intuition. Which, I mean, fair enough; I started writing Go with a C++ intuition, with different questionable practices ;)


Yeah, that would work, though then I need to worry about all the local variables too (which I didn't use in the example but did need in the actual code). Still, not a major overhead since I can use a context object for that. Anyway, my issue was with the vtable dispatch of interfaces more than with type switches -- using a .Draw method is pretty much the same :)

But yes. It was written with Rust intuition, and whilst I ried to learn the Go way of doing things, I obviously didn't pick up everything :P

Thanks though; I'll try to use this pattern in the future!


I'd love to post a Go-best-practices article on Polyglot Weekly, as a rebuttal. If you or anyone else reading this thread would be interested :)


Glad you pointed this out. Hopefully the original author will make a note of that on his blog because when people run across it they will be given the wrong impression about how this would be handled in Go.

I love how "opinionated" and slim Go's design is. The less variation in code, the better, in my opinion. It might not be as "fun", but frankly I have a lot more fun programming when I don't have to deal with esoteric solutions to problems.


I'm not so fond of editing an already published post, but I'll ask bcoe if we can/should do that and add some clarifications.


Would it be reasonable to create a separate channel for each message type, and switch over all of them in the receiver?


that sounds heavy to me.

Generally using multiple channels and select is good when you have multiple threads sending. When there is a single sender, putting everything in one channel feels better to me.


Dynamic dispatch isn't really a replacement for true pattern matching, as pcwalton explains downthread. Local variables (which would be represented as some sort of scope/context object) are one problem, but so is encapsulation. Why would you make the assumption that (X data) knows how to draw itself? What if you're doing something like handling system events? The only true replacement here would be the visitor pattern—which itself is a PITA for a bunch of reasons.


The unused imports thing seems to be a common complaint, however I just use go-imports, and imports are added/removed automatically when I save the file.

I never ever have to even touch imports, except sometimes when two different packages have the same name.


This doesn't help with unused variables, which comes up just as often when doing the same thing (coding piecemeal, testing if it compiles, etc.)


  var x int
  _ = x  
  
Mildly annoying, but it avoids unused variables in any code which compiles, and is a helpful reminder of "I was going to do something with this value".


It seems like a warning only lint would be less annoying and a better reminder.


It is an established fact that humans cannot be trusted to handle warnings responsibly.


Judging from this thread Go users are regularly using various tricks and hacks to avoid the compiler errors. So evidently Go programmers can't be trusted to handle compiler errors responsibly and not use hacks to hide them. In which case, what is the point of making in an error? It just makes things more difficult for the responsible programmers who don't ignore warnings (instead of a warning that they will notice they have to find and remove an obscure line of code).

It was an interesting experiment to make these things errors only, but it's clear that it has failed.


I wonder if we can fix the "warnings ui" somehow, so that they work better. The fundamental tension seems to be that programmer thinks he knows what he is doing, adds a warning override and the warning goes away. But then it may turn out that he didn't and the warning indicated a real problem.

The problem is the S/N of warnings. Our programmer removed a warning so that real warnings wouldn't drown in a see of false warnings.

The question is then, can we have our cake and eat it too? We don't want to completely forget about low risk warnings, we just almost completely. Maybe we could have some kind of exponential backoff when you ignore warnings?


Why not use a comment?


Oh that's handy; I wish there was a flag so it could do that during compilation so I'd never have to worry about it again :)


Note: this was originally posted at http://inpursuitoflaziness.blogspot.in/2015/02/thoughts-of-r... , however I thought it would be nice to go through the Polyglot Weekly editorial process and come up with a better version of it :)


Having next to no Go experience, and zero Rust experience, I found this article a really fun read. Coming from a Node.js background, it was neat to see the concurrency approaches used in both languages compared -- quite different from a single-event-loop :)


I'm sure it just comes down to personal preference, but as a node developer myself (perhaps former), i found it so freeing to switch to Go. Mainly for two reasons (which i assume are less common reasons):

1. Synchronous by default 2. Interfaces

Point 1 to me feels silly to admit. But man, i still do a fair bit of JavaScript/Node for work (our frontend, mainly) and it is just so.. tiresome to me, to constantly feel on the edge of bugs because some functions are expected to be synchronous (return values) and others are intended to callback.

I'm not even talking "nested-hell", i'm simply referring to the mental overhead that i apparently use when using any JavaScript function. It's not difficult.. it's just not enjoyable. It's.. tiresome for me.

Point 2 is simply because i really like interfaces. Back from my Python days, a few libraries had invented ways to support interfaces and i really really liked them, but not the overhead they required. Being able to expect the behavior of an object with certainty is the main part of duck-typing to me, and seeing that from a more static language (when compared to python/js) is really enjoyable.

I know you didn't ask for a writeup, i just had to agree with you and felt the need to share my experience from a similar standpoint. :)


I love the Node/npm community, and have been programming JavaScript for so many years it's hard habit to kick :) I'd love to try my hand at Go though... I think it's awesome to see what practices can be shared between communities.

One approach I've been playing with in JavaScript-land is using Promises for all return values, this means that you consistently know how to interact with a return value, whether its concrete-value is available immediately or at a later time -- this doesn't really solve the problem of consuming other people's libraries however!


But that’s the issue, duck typing really leads to many situations with uncertain situations. It’s like the + operator in JS: depending on context, you can have appending, addition, or vector addition.


Are you referring to duck typing in general? Or specifically Go's interfaces (which i referred to as duck typing)?

They seem rather harmless to me, so i'm curious on your perspective. I see them as no more dangerous than an explicit type (or any function, for that matter).

You're asking for a specific behavior, and you know that what you get will have that specific behavior. A Reader or Writer is a simple example of that. Sure, you don't know what it's writing to, but that's outside your scope and likely the scope of the function - right?

Perhaps i misunderstand you, i'd love further explanation/examples _(pertaining to Go's interfaces)_ :)


Imagine I get an object, I don’t know it’s type – I can’t even check if it implements the VectorAddition, Addition, or Appendable trait, I only see it has a .+ method.

How am I supposed to switch between types being able to do either of these, without listing all types that might occur – as this might not be possible

(I’m thinking about Haskell’s type classes as a solution to this, btw)


Plus is a bad example because you can't do that in Go, but I understand the question.

Basically, you don't do that in Go. Go has interfaces, but they're implemented implicitly. So rather than "I get an object", you say, "I get an object that implements interface X", and you use whatever methods X has.

That just means its up to you to name interfaces in a reasonable way. Which is one down side - if you have an "Add" method, it's back to your question about what it means to add, depending on the implementation.

Nothing does this as well as Haskell as far as I can tell.


Yea, as mentioned by vectorjohn, you actually just create an interface for whatever you need.

So in your example, you might have an Adder interface that simply checks for the Add() method, but if you know you're going to need VectorAddition, Addition, and Appendable, make a new interface that implements all three, then use that.

You can do this in one large interface, or 3 small interfaces that you use to compose a 4th, larger interface (usually the preferred way). Example:

    type Adder interface {
      Add()
    }

    type VectorAdder interface {
      VectorAdd()
    }

    type Appender interface {
      Append()
    }

    type VectAddAppendable interface {
      Adder
      VectorAdder
      Appender
    }
Make sense? Forgive any lapses in syntax, it's been a long day. But i think this is the solution you were referring to correct? By saying that the object coming in must implement the VectAddAppendable interface, you can be positive that it implements the methods you care about.

A good example of this is in Golang's stdlib IO package. There's Reader, Writer, and ReadWriter. ReadWriter simply requires both the Reader and Writer interfaces. It's a very common paradigm.

Does that solve your issue?


> I'm not even talking "nested-hell", i'm simply referring to the mental overhead that i apparently use when using any JavaScript function. It's not difficult.. it's just not enjoyable. It's.. tiresome for me.

Pretty much why i never got into node development.


My biggest problem with Go is this one [1]

In short, assign nil to a variable, and it ends up not being equal to nil (?!?) Really, how does something like this get approved?

[1] https://golang.org/doc/faq#nil_error


A nil pointer is a perfectly valid value that can satisfy interfaces.

http://npf.io/2014/05/intro-to-go-interfaces/#toc_4


Yes, that one bit me quite hard a while back. It was rather surprising.


> Eventually I was able to learn the proper style by watching my code get corrected.

Funny thing: I actually benefit from not typing idiomatically! GoSublime runs gofmt whenever you save a file. It's a free check for syntax errors, as it won't fix the formatting if you made a mistake. And because the mistake must be in the non-idiomatic formatting, it's easier to spot too.

> I'd love to see a rustfmt, however!

Seriously, I miss gofmt in any language that doesn't have a similar feature. I'm so surprised it hasn't been copied more!


Reminded me of the old Visual Basic, in which you'd declare your variables with caps but write their usage in lowercaps. This way the instantaneous auto-capitalization checked for you that you had written the name correctly (instead of implicitly defining a new variable!).


Even today, auto capitalization fixing (and just auto-formatting code as you type in general) is one of my favorite features of Visual Studio, one that I wish I could have in every IDE/editor.

For example, if your class is named FooBar and you are declaring a FooBar variable by typing foobar, Visual Studio will just auto-fixit as you type. Same goes for properly formatting if statements, declaring functions required by interfaces, etc... In comparison, Xcode requires you to "confirm" the autocompletion fix by hitting enter, which is less ergonomic in my opinion.


Totally agree.

When I started writing python on my own, I dislike some of the PEP8, I liked two spaces, not 4, etc... But then as soon as I started working with teams, my personal preferences no longer mattered, sticking to PEP8 make made life way easier.

I think eventually we'll just store the AST in our version control systems, and use interfaces that we will adjust to our preferences when editing, diff'ing etc...


Interesting point of view to hear. RE: enums - There is an idiomatic way to do enums in go I believe:

  type MyEnum int

  const (
        A MyEnum = iota
        B
        C
        D
  )


Rust's enums aren't like Java enums, they are algebraic datatypes and each variant can hold different data. Check out the example with DrawTriangle and whatnot for what I mean :)


You are not correct regarding Java's enums. Define properties for your enum type, widen the constructor appropriately, and provide the accessor methods:

https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html...


That's not the same thing. Rust enums are types, each with the possibility of having different fields that store data. Java enums are instances of a type, each with static data.

(rust enums can have associated data too, but its not their main usage in idiomatic code)


Why does Rust incorrectly use the name "enum" for "tagged union" or "sum type"?


Because in the simplest case when no variant has any associated data they are indistinguishable from C-style enums. The `enum` keyword was originally `tag` in ancient versions of Rust, and at least twice in the past three years there have been massive bikesheds to reconsider the keyword. You are free to dig through Rust's history if you'd like to understand why `enum` won out every time.


They don't have to be though, a degenerate enum `enum Foo { Bar, Baz }` is equivalent to a C enum.


Rust's Enum, it looks like, aren't the same as C-esque enums, and look a lot closer to Go-interfaces (or like the article says C's unions).

That said I wonder why they named it enum.


> That said I wonder why they named it enum.

Because it's a type-safe enumeration? ADTs are pretty much a superset of C-style enums, an ADT with all-dataless constructors is equivalent to a C enum:

    enum Foo { Bar, Baz, Qux, Quux }
And members can converted to integrals:

    > println!("{}", Foo::Qux as u8);
    2
With explicit integrals very much like C:

    > enum Foo { Bar = 5, Baz, Qux, Quux }
    > println!("{}", Foo::Qux as u8);
    7    
Calling it "enum" makes it familiar to C developers[0], "union" would have been confusing for the same (as you can't just write one representation and read the other) and "type" is a tad too generic (and used for type aliases à la typedef).

[0] even more so C++ developers as they have `enum class` which is also type-safe and not implicitly converted to integrals


Well you are right, the name does make sense, but I would have thought "union" would have made more sense than "enum" for those of us from the C-family.

OTOH I haven't written a line of Rust of yet, so enum might be more familiar in use.


> Well you are right, the name does make sense, but I would have thought "union" would have made more sense than "enum" for those of us from the C-family.

The issue with `union` is it might hint at things which are not available (e.g. the union of an int and a float where you can access to the raw data as either).

A Rust enum is really an encoding of the `struct { enum, union }` pattern, but it degenerates to an enum if there's no union part (no data payload), not to a union. So calling it an enum makes more sense.


What Rust calls an "enum" is a discriminated variant record from Pascal. They're used in a completely different way, though. Their primary use is encapsulating function results which can be either a useful value, or some indication of error. The "Result" and "Some" generic enums are used for this. Then the "match" operation is used to fan out control depending on what enum case came back from a function. It's both elegant and kind of clunky.


Tagged unions in particular, C's version of variant types. An enum can be considered a degenerate case of such a type, so the naming isn't totally arbitrary.


mmm, this seems to be a confusion of names. Using const and iota in Go helps you to create enumerations (http://en.wikipedia.org/wiki/Enumeration) whereas enum in Rust is actually creating a tagged union (http://en.wikipedia.org/wiki/Tagged_union). Most of the time in Go, you would define a new interface for the situation the author is describing.


I think what he actually wanted was:

    type Shape struct {
            Rectangle *Rectangle
            Circle *Circle
            Triangle *Triangle 
    }
Then you send shapes on your channel rather than interface {}. If you want to enforce having only one element populated, make the struct members private and write:

    func NewRectangleShape(r *Rectangle) *Shape {
        return &Shape{rectangle: r}
    }


No, please don't. Go structs are not C unions. Please see my other comment in this thread about using Go interfaces.


I disagree. Sometimes an interface is not appropriate; just because you have two things that are alike doesn't mean that they implement an interface.

"A circle, triangle, or rectangle" is not the same data structure as "Something that can be drawn". Certainly if you have a function that draws something given a Draw() method, having the function take a "Drawable" is appropriate. But it's not the only use case; sometimes you really mean "A circle, triangle, or rectangle" and in that case, a struct is absolutely what you want.


What about compilation speed of Go/Rust? Surprisingly there wasn't mention about this in article. I consider slow compilation as a main issue of Rust compared to Go. Am I right?


Rust is very slow, yes.

However, I was writing a rather tiny library so it wasn't a major factor, so I didn't notice this until much later.

I did intend to add a blurb about this when posting to Polyglot Weekly, but I .. forgot ;P


thanks for answer)


Having written in both Rust and Go, I found that a very good article.

The big new idea in Rust, of course, is the ownership model and its compile time borrow checker. That's worked out surprisingly well. I expect to see that idea appear in future languages. One could have a Go-like or Python-like language with a borrow checker. It's not inherently tied to Rust.

As a practical matter, programming in Rust is more likely to hit problems with type checking than borrow checking. Ownership problems are usually with your own code. Type problems often involve code written by others, and changes to other modules often induce type errors. Because Rust has type inference, and because it's often not obvious what the type inference engine is doing, Rust can have rather abstruse type problems. Go is rather straightforward in that area.

Go lacks generics and templates. This simplifies the language, but the lack of generics then has to be worked around. Go has too much use of "interface{}". Bolt-on compiler passes such as "go generate" and the protocol buffer compiler have been added. Both are used mostly to get around the lack of generics. Rust has very powerful generics and templates, so powerful that they tend to be over-used. Everything seems to need a generic. Rust is starting at the cruft level it took C++ 20 years to reach. This may or may not work out in practice. It may be necessary to restrain Rust template enthusiasts from getting carried away.

Error handling in both languages is via return values. Go is straightforward, with multiple return values being the normal approach. The Rust mechanism, involving Result<> and Some<> types, is rather clever, but results in wordy code to do simple things. Too much Rust code goes into putting things into Result and Some objects, then analyzing and dismantling those objects. It's not clear that leaving exceptions out of Rust is a win. Negative experience with C++ exceptions may have motivated that design decision.

Rust has Resource Allocation Is Initialization (RAII). Go does not. For both languages, problems come up with resource release. Go has "defer()", which is kind of clunky and can't be encapsulated, while Rust has destructors called when something is deallocated. In both cases, it's hard to handle an error at resource release time. Destructors that do anything non-trivial are usually troublesome. Both languages have this problem. Languages with "with" (Common LISP's "(with-open-file ... ...)" and Python's "with FILEOBJ as HANDLE : " have fewer headaches with resource release errors, since they occur in ordinary code, not at some special destructor time.


In rust, can't you just use a {} block to force the scope limitation?

(I've just browsed the rust docs, never written anythign in it)

    fn do_stuff() {
      // already inside a function!
      {
        // allocate here...
        let x = Giraffe();
      }
      // should be deallocated by now. right?
    }


You can certainly limit scope in Rust. That's not the problem, though. The problem is, what if something goes wrong in Giraffe's destructor? Like maybe the remote procedure call to Zoo fails.


goimports-on-save is an essential tool in your writing environment for go. It's not mentioned in the official docs, but using it completely obviates fiddling with your import declarations ever again. While you're at it, you might as well turn go vet-on-save and go lint-on-save as well.


Yeah, it's been mentioned to me in this thread many times :) I so wish I had known beforehand ;p

It would be nice to have a cheatsheet full of Go tips & tricks like gofmt/goreplace for replacing idents, and goimports-on-save, and whatnot. TBH I didn't really look for such non-in-built solutions when starting though, so I would have missed it anyway :P


> I didn't really look for such non-in-built solutions when starting

I can't fault new users for this; the non-discoverability of these awesome tools for new users is a genuine fault of the Go ecosystem.

Just a "Tips" or "Useful Tools" list at the end of the Getting Started page or the How to Write Go Code page would be better. Just to make users aware that such tools exist.


"Unlike Rust, Go is really easy to pick up. It's possible to jump directly into it, and you can be writing useful programs after a single afternoon of messing around or reading."

I think it's safely implied that you put more effort into Rust code, but get more out of it. It's a tradeoff from Go. It's worth it for some programs, and others it's not.

Go for me doesn't work out because it's not C ABI compliant which is a limitation Rust doesn't have. As a language nothing draws me to it in particular. The killer feature is how easy it is to get started with it. Something that is undersold.

Coming primarily from Python I'm still favoring the field over Go until my problems matched Google's. I would probably reach for Erlang for concurrency and Rust for anything low-level going forward.


The most important thing I took away was that Rust has enums as tagged union types; I cannot count the number of times I wished I had this in my day-to-day language.


"Unlike Rust, Go is really easy to pick up. It's possible to jump directly into it, and you can be writing useful programs after a single afternoon of messing around or reading. On the other hand, while basic Rust is easy to pick up, it takes a while to get used to the borrow checker (and in general understand ownership/borrowing). Additionally, most libraries make full use of advanced features (like associated types) and one needs to learn these too to be able to use the libraries."

This is pretty much why I consider these two to be in less competition than first meets the eye. Rust intrinsically involves putting more effort in to the code. You put more effort in and you get much more out, but there's a lot of code for which that's not the right tradeoff, just as there's a lot of code for which it is.

"In Go there's no obvious way to get this. The closest thing is the type called interface {} which is similar to Box<Any> in Rust or Object in Java.... Of course, I could implement a custom interface MyMessage on the various types, but this will behave exactly like interface{} (implemented on all types) unless I add a dummy method to it, which seems hackish."

I think most Go programmers stick to interface{}, but personally I put the tag method on the type, being from the Haskell side of the house on that front. In practice it turns out that often the "tag" interface grows a useful method of some sort, at which point it ceases to feel silly. If nothing else, you've got the ".Handle()" option. See also http://www.jerf.org/iri/post/2917 .

Also, just generally on the performance front... developers who care about performance are at this point subconsciously pretty used to being able to choose between "language like Rust or C++ that really, really cares about performance, but is hard to use" and "dynamicly-typed language that's really flexible and powerful but 50-100 times slower than the other choice". In the limit, Rust will absolutely be faster than Go, no question, but the performance penalty may be more like a factor of 2 in the end... Go's pretty fast, and while you can't go absolutely crazy with optimization C-style, you do get to play with locality optimizations with real structs and real arrays and such. Worrying about the performance differences between C and Python before you write a line of code may be perfectly valid, because the gulf is pretty large... worrying about Go vs. Rust is going to be a much narrower band of validity before you're just prematurely optimizing. Rather a lot of high performance code still has "vtables" in it, used quite extensively.

In practice, I don't expect many people to decide between Go and Rust based on performance needs on similar code... non-zero, yes, but not many. What expect people to choose between them are general software engineering complexity considerations. When it comes down to it, the reason to write Servo in Rust and why Rust needed to be created to do it isn't that there weren't languages that were "fast enough"... it's that the browser has bat-shit insane sharing patterns, complexity beyond the mind of any mere mortal, and you end up with code that either does way too much copying or crashes when it doesn't do enough, because there's no ownership management in the language. That's the sort of reason to choose Rust over Go. If you're making something that looks like a browser, or an office suite, or a big highly-integrated business workflow suite, or something like that, I'd choose Rust over Go in a heartbeat simply because Rust is designed to shine in that environment, and Go ultimately doesn't particularly offer anything to make that scale of that sort of code any better than any other statically-typed language.


Speaking as one of the founders of Servo, the reason why it couldn't be written in Go is absolutely about performance.

As far as we're concerned, the most compelling reason for Servo to exist is performance, especially in layout and rendering. Many of the wins of Servo layout have come from carefully coding to the optimizer to allow it to eliminate virtual dispatch and procedure call overhead, break apart structures, stack-allocate where possible, and (in the future) use SIMD. As a result, the performance profile of most of Servo layout tends toward flat loops as opposed to deeply nested stacks of virtual call upon virtual call—much friendlier to the branch predictor, i-cache, and intraprocedural optimizations. At the same time, we get a lot of responsiveness wins from having a threaded design where pauses in one thread—most importantly, JS and the DOM—do not impact responsiveness in the other threads, which becomes very important when CSS animations are involved, especially common ones that require reflows.

Now with Go, we'd have the choice between a compiler that basically doesn't do any optimizations beyond very simple ones (6g/8g) and a compiler that lacks good GC integration (gccgo). The former would negate all of the advantages of our careful coding to the optimizer and would probably make Servo layout, in parallel mode, slower than the sequential layout of other browsers. (Despite what djb says, compiler optimizations really matter!) And the latter would cause the stop-the-world GC times to spike, jeopardizing our smooth animations, which arguably matters even more than raw reflow performance to the user experience. (That's not even counting the fact that since Go's malloc is reportedly extremely slow compared to that of Java, it relies heavily on escape analysis, which gccgo doesn't do…)

There are other reasons too, such as the fact that integrating a good JS garbage collector and Go's garbage collector would basically be impossible without rewriting one of them and making major sacrifices along the way (like making JS GC multithreaded or making Go's GC conservative, like Blink did with Oilpan). But performance is the main reason.


"As far as we're concerned, the most compelling reason for Servo to exist is performance, especially in layout and rendering. Many of the wins of Servo layout have come from carefully coding to the optimizer to allow it to eliminate virtual dispatch and procedure call overhead, break apart structures, stack-allocate where possible, and (in the future) use SIMD."

Yes, but if that were the only concern, you wouldn't need Rust to do it. C++ already exists, and it can do all those things. (Well, the "break apart structures" may be a bit unclean depending on exactly what you mean, but that alone wouldn't be worth writing a new language for.) My interpretation of C++'s raison d'etre in modern times is "zero-cost for abstractions you don't use", whatever its heritage may have been in the past. If that were the whole story, you just would have started a new C++ renderer from scratch.

You need Rust because actually writing that code in C++ would be unmanageably complicated due to the ownership problems that Rust addresses. C++ is already unmanageably complicated (IMHO) when trying to write a browser engine, also requiring that level of performance would basically take it into the realm of the impossible.

You need the complexity management first. Managing the complexity buys you the cognitive budget to write the fast code, too. It is not as if the C++ render authors do not want to write fast code, or as if they are unaware of the recipe you just laid out.

I said I wouldn't write it in Go because it is already disqualified due to the complexity issues. Even if I didn't care about the performance gap, I still would have no desire to write a browser engine in it because it would be a nightmare, just like the C++ engines are, and for pretty much the same reasons, in addition to others. The performance gap between Rust and Go here is essentially unmeasurable, because in this task, Go won't even cross the finish line. (By my standards, at least. I consider the C++ renderers to also not cross the finish line. The fact that memory-unsafe code can be bashed into submission with sufficient man-millenia doesn't mean I have to agree that memory-unsafe programming languages are a good idea.)


Oh, I definitely agree that safety is the main reason for choosing Rust over C++ in particular. The slightly better alias analysis and dereferenceability-of-pointer guarantees that Rust can provide over C++ are cool, but I don't think it'll make or break our layout engine.


I'd like to see Rust put more focus into making SIMD work nicely. I think Rust is a great alternative for C++ needed projects -- but it completely lacks SIMD in at least the upcoming 1.0 release.

More importantly, there isn't any language that focuses on _making SIMD work right_ -- even C/C++ do an awful job of this with it's tacked-on __mm_foobar functions.

Rusts idea of having SIMD live as near-primitive datatypes (f32x4 etc) follows Dart a bit and overall I think it's a good approach -- albeit very incomplete right now.

I seriously hope that emphasis will be placed into making SIMD feel like a first-class citizen in the language and not a tacked-on feature as I've seen in so many other languages.


I agree that we need to work on SIMD post-1.0. I really want to make use of it in Servo :)


I am the author of current SIMD support in Rust, and my tentative plan is to implement something like Sierra, a SIMD extension for C++.

http://sierra-lang.org/


Re performance: It's not a question of raw speed, it's a question of predictability. GCs introduce unpredictable hiccups and require significantly more RAM (GCs are happiest when the working set is very small but the heap is very large - basically, the greater the percentage of RAM used is garbage, the happier the GC will be)

Whether or not you can afford this very much is an upfront thing. You cannot push something like this off until later. For things like a web browser or game engine or other highly-interactive, highly-latency-sensitive, high-ram-using thing it's known far in advance that the GC will be a curse.

This is why C++ continues to be the language of choice for those sorts of systems. Control over memory is crucial for them.


Fun fact: The professor who asked us to use Go in the course also knew Rust and Nim and in general liked programming languages. When I shared the original version of this article with him, he said this interesting tidbit (which I agree with wholeheartedly):

> ... there is no other language I can think of with such a small surface area that I can tell students in the first week to learn and deliver an assignment by the second week

...

> This is pretty much why I consider these two to be in less competition than first meets the eye. Rust intrinsically involves putting more effort in to the code. You put more effort in and you get much more out, but there's a lot of code for which that's not the right tradeoff, just as there's a lot of code for which it is.

I don't consider the two to be in competition (aside from "which hip new language should I learn today") either, though it's less due to the easy-to-pick-up thing and the Rust mentality that:

- If it can be a footgun, be explicit (unsafe, exhaustive matching, unwrap)

- If you can do it at compile time, DO IT DO IT DO IT DO IT (all the compiletime type safety, ownership, etc). All abstractions should be zero-cost or with minimal cost (at runtime). Try to write your code so that tests won't be necessary (because a successful compile should be a test), and then write tests anyway!

(This mentality is pretty prevalent in both the Rust stdlib/language and external libraries. The equivalent of interface types in Rust (`Box<Trait>`) is generally frowned upon and only brought out when absolutely necessary, for example)

Go doesn't really embrace either of these principles, so it doesn't compete that way. You're right, developers already are used to making those choices.

> worrying about Go vs. Rust is going to be a much narrower band of validity before you're just prematurely optimizing. Rather a lot of high performance code still has "vtables" in it, used quite extensively.

Not necessarily. As mentioned before, the Rust way of doing things generally leads to a lot of optimized stuff. Taking the vtable example -- Go code that I've seen uses vtabley things everywhere (the stdlib is peppered with interface types). Using vtables isn't bad, but when you use it for stuff that could have been solved by static dispatch (for large codebases -- that can be everywhere), then it can become an issue. In Rust a vtable is a last-resort that people will grumble about before picking up and using (but will use it whenever absolutely needed). Sure, a single vtable isn't much of a performance optimization, but when all your APIs use static dispatch due to this mentality, the collective optimization is huuuge.

> In practice, I don't expect many people to decide between Go and Rust based on performance needs on similar code... non-zero, yes, but not many.

For many applications, I agree. Systems programming is one where this doesn't apply, but for stuff like a distributed consensus algorithm implementation (which I was doing), both Go and Rust are perfect. And I'm pretty sure that if I'd had more practice in Go, I would have been confused which language to write it in. (Right now I love Rust and am not great at Go so of course I would use Rust if given a choice)

For stuff like a browser, it's not so much that there are batshit insane sharing patterns, it's more because a browser is huge and performance matters a LOT. (I think pcwalton above explains why better than I can)


> > ... there is no other language I can think of with such a small surface area that I can tell students in the first week to learn and deliver an assignment by the second week

As a lecturer, Python would be my other choice, nowadays, for a small surface area language.

I suspect Go has the advantage here simply because it barks at you at compile time over certain errors. Pedagogically, something that students have to run that checks the syntactic validity of their program is probably win.

I emigrated from Lisp->C->Tcl->Perl->Python over about 30 years and Python is still the thing I reach for when I have to whip up a program (yeah, Lisp was nice but I could never afford a machine with enough memory for it not to be a toy--memory was expensive at one point, you youngsters).

Go doesn't really interest me that much. It's a little better at a broad range of things, but nothing that pulls me from the existing set of languages. I'd rather use Erlang for concurrency, for example. Maybe if I had Google-sized codebases I'd care more.

Rust interests me because it is in the same range as most languages for most things but is a LOT better at a very few things.

I'm waiting to hear somebody use Rust in a video game engine. I suspect that C++ will vaporize (one can hope) once someone proves that Rust works in production.


Yeah, Python works too, though Go is great at concurrency in a way Python isn't (we were spawning loads of goroutines for tests without any issue, for example, and channels are well supported) so it fits better.

I too am a Python fan for scripting though :)

> Rust interests me because it is in the same range as most languages for most things but is a LOT better at a very few things.

Me too, for similar reasons. It's like C++, but without all the stuff I didn't like about C++ :)

> I'm waiting to hear somebody use Rust in a video game engine.

http://github.com/PistonDevelopers/piston


The ability to use OpenGL in a way that doesn't suck would be nice. Thanks for the pointer.

I need to think about this a lot. I've got a particular project in mind (VLSI design) and the thing that has always stopped me is that the UI toolkits can't deal with concurrent actions. OpenGL and multi-threading are asking for disaster with C/C++.

Rust might let me break that logjam.

Clojure was close, but JOGL still remains a disaster.


"Not necessarily. As mentioned before, the Rust way of doing things generally leads to a lot of optimized stuff. Taking the vtable example -- Go code that I've seen uses vtabley things everywhere (the stdlib is peppered with interface types). Using vtables isn't bad, but when you use it for stuff that could have been solved by static dispatch (for large codebases -- that can be everywhere), then it can become an issue."

Yes, but by definition, we're discussing optimized code here.

http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?t...

Yeah yeah, benchmarks blah blah, but the point here is, compare with, oh, say, this:

http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?t...

As I said and will repeat without blinking, Rust will always be faster than Go (and the problem in the shootout Go beat Rust on is in my opinion certainly simply an unoptimized Rust implementation, no question), but language deltas of 2x or 3x in practice are dominated by algorithms and IO far more often than 50-100x. For all the long list of reasons that Rust is faster than Go, all of which are true, they add up to much less than developers are used to. Intuitively, when you hear "go is slower than Rust because vtables and bad optimization and GC and its pauses and interfaces" you probably mentally expect Go to be like 10 times slower, when instead it's 2-3.

This is relevant because I don't think Rust advocates should sell "speed!" too hard as the primary thing they have... C++ will pretty much forever be able to keep up on the raw speed front, especially in benchmarks. What it has is complexity management, at speed. But complexity management first.... Rust could probably take a straight-up 2x speed penalty over C++ and still write a renderer than ran faster than C++, because it'll be a better renderer.


> unoptimized Rust implementation

Scotty! We need more CORE[s] !"


Oh, I see what you mean now. I agree to the most part :)


> Fun fact: The professor who asked us to use Go in the course also knew Rust and Nim and in general liked programming languages.

Sounds like an awesome professor! It's a pity he didn't get you to use Nim.


My hat is off to the author of this piece; what a beautifully written fair evaluation of two technologies.

Kudos.


Here's part of why the interface thing is useful: it gives you dependency injection almost for free, and it allows modules to be extremely decoupled. You can create a module-local interface describing not just a "generic X" but "specifically what methods of X I want". That way you don't even depend on other object's types, but only on their permitted actions. And making mocks is very easy. The IO libraries use this extensively; they don't depend on where you're getting your IO from. They just require it does this or that.

Bonus, you can define "tolerable minimum" and "nice to have" versions of the same local interface. Define your methods in terms of the first, but attempt to cast to the second.

  if nice, ok := minimal.(Nice); ok {
    // do stuff only possible with the nice to have interface
  }
Example: the HTTP libraries will flush your IO if you give them something flushable, but they don't require it.


It seems he approached this in good faith and gave an honest review. Which seems quite rare for people commenting on Go.


I might be mistaken, but `func bar(){}` is not public, I think you meant to capitalise the "B".


fixed, thanks!


My biggest gripe with Go: Unused variables. Unused anything doesn't let me go on with coding. It's obviously a design decision from the makers of Go, but the way I usually program is "experiment first, then clean up".


We used to have `#![deny(unused)]` in Servo, and it was quite annoying. Especially because a compile takes a while and the lints run mid-compile (after type/borrow check but before the llvm stuff). Quite annoying, eventually we turned it off. But it wasn't so bad; because when writing Rust code there's more fixing of type/borrow errors and less waiting for a full compile and running tests; since most of the "correctness" is encoded in the types used.


There's a way to work around that, a bit of a hack but it can help when quickly iterating:

    _ = unusedVar


Regarding enum support, you can create type safe enums in Go like this:

    type Color int

    const (
      Red     = iota
      Blue
      Green
      Sausage
      Yellow
    )
The `Color` type is not compatible with int, so except for explicit casts and literal assignments, it's type safe.

Though that's quite a different enum style compared to the tagged unions of Rust, his approximation with interfaces is indeed closer to that.


but that's only a simple c-style enum. What the author is describing is a algebraic data type/sum type: an alternative between types with possibly different types (i.e., a Boolean, two strings, etc.). Read the example in the article closely and you will know what I mean.


Looks like an error in the Go code in the What I didn't like/No enums section. In the paintingLoop function the type assertion switch should be on the message and not the channel.

Should be "switch msg.(type)" instead of "switch ch.(type)"


thanks; fixed!


Re: Go errors for unused imports

> This rules out some workflows where I partially finish the code and check if it compiles.

Sounds like a great job for an IDE to automatically add/remove the appropriate imports for standard library stuff.


Goimport (i believe is the tool name, i use it for Vim) does just that. Also, because of the namespacing in Go, it actually works for non-standard library imports as well. It runs along with Gofmt on every save for me.

The only time in my experience that it has trouble is when there is a namespace conflict and it has to guess. Generally that's easy to avoid though, and uncommon.


Atom and the Go Plus Package do exactly that. Very convenient!


The problem with Go is that you don't grok Go interfaces.

EDIT: downvoters, please explain what's wrong with my comment.

Go interfaces are quite a novel concept, and most developers that jump from language X to Go don't have enough time and experience to understand how they work, and how to design their application around this new model.

Their example of the messaging system/UI application is a straight port of the Rust code, and they lament the fact they have to use interface{} to overcome the lack of object orientation or pattern matching.

    func paintingLoop(ch chan interface{}) {
        // shorthand for a loop over receiving over
        // a channel
        for msg := range ch {
            switch ch.(type) {
                case Quit:
                    // quit
                case DrawLine:
                    // cast to a drawline message
                    line := msg.(DrawLine)
                    // draw `line`
                case DrawTriangle:
                    tri := msg.(DrawTriangle)
                    // ...
                case SetBackground:
                    bg := msg.(SetBackground)
                    // ...
                default:
                    // Need a default case in case
                    // some other type is fed through here.
            }
        }
    }
This is not idiomatic Go. The author here should be leveraging Go interfaces and they wouldn't lose type safety.

    type Drawable interface {
        Draw()
    }

    type Line struct {
        x1, x2, y1, y2 int
    }
    func (l *Line) Draw() { ... }

    type Circle struct {
        cx, cy, radius int
    }
    func (c *Circle) Draw() { ... }

    func paintingLoop(ch chan Drawable) {
        for object := range ch {
            object.Draw()
        }
    }

    func main() {
        ch := make(chan Drawable)
        go paintingLoop(ch)

        ch <- &Line{x1: 10, y1: 10, x2: 50, y2: 50}
        ch <- &Circle{cx: 10, cy: 10, radius: 20}
    }

You wouldn't expect Python programmers to try Haskell over a weekend and have enough experience with it to write a blog post about "Thinks I dislike about Haskell", but it seems much to often people play with Go and, when they fail to translate their ideas to this new language, post yet another "rant" on HN about how Go sucks.

Go has its fair share of shortcomings, but try to _learn_ the language before writing yet another misinformed article.


This still has a vtable creation/dispatch overhead in your example (if I wasn't clear, this was my larger concern, not the type switches). It also has troubles with local variables, though they can be solved pretty easily.

But point taken. I will try to use this pattern in the future.

> Go has its fair share of shortcomings, but try to _learn_ the language before writing yet another misinformed article.

I believe I was pretty clear in the article that I didn't know the Go way of doing things and this was about what Go looked like from a Rust point of view. This was not supposed to be a rant, just a post that sort of illustrates the differences between the languages. IMO it actually gives a clearer idea of what Rust is like than it does of Go since the "Rust way" of doing things is clear from the comparisons..


> Go interfaces are quite a novel concept

They're not: https://realworldocaml.org/v1/en/html/objects.html#ocaml-obj...

> This is not idiomatic Go. The author here should be leveraging Go interfaces and they wouldn't lose type safety.

You've conveniently ignored the case of the non-drawing commands. Should they be Drawables with a Draw which does not draw anything?

> Go has its fair share of shortcomings, but try to _learn_ the language before writing yet another misinformed article.

The OP has been upfront about his limited knowledge of Go, and his rust knowledge coloring his thinking.


> You've conveniently ignored the case of the non-drawing commands. Should they be Drawables with a Draw which does not draw anything?

In particular, you can't implement Quit using a virtual method, unless you do something like have every function return a boolean to the caller indicating whether it wants to quit (or panic and recover, but that's too awkward for words).

You also can't have methods that set local variables in the caller, unless you do something like pass in their addresses to the function, which gets awkward quickly if you have a lot of local variables.


> You also can't have methods that set local variables in the caller...

How would you do that using ADTs?


You don't use methods, but instead switch on the discriminant of the ADT and take different actions (such as updating local variables).

This is pretty related to the so-called expression problem. OO-style methods are good at extensibility, but they're bad at adding extension points (method calls), since you have to update every one and calls are limited in expressive power compared to the body of a switch statement. Functional-style ADTs are bad at extensibility (adding a new type requires updating all pattern matches), but they're good at adding new extension points, since you don't have to touch the data types and pattern matching is powerful. Supporting both styles in a language gives programmers the freedom to choose the best one for the task at hand.


Makes sense. Thanks.


With ADTs you would not have individual methods, but instead a match block possibly referencing the local variables.


I think the general pattern is to use a context object, but this gets complicated sometimes.


The author is upfront that this article is about first impressions. He freely admits that he is probably not writing idiomatic Go and as a result his first impressions are skewed.

That doesn't mean the article isn't interesting or useful though. It's helpful for any language community to see what a new comers first impressions are and then tailor the onramp appropriately in their documentation.

You are unneccessarily hard on the author here I think.


The author here should be leveraging Go interfaces and they wouldn't lose type safety.

This assumes the structs themselves know how to draw. This is usually (not always) an antipattern, because it conflates data with behaviour.

I should be able to swap out the drawing implementation without having to change the data. For example, given a circle (cx, cy, radius) I should be able to draw it using SVG or OpenGL.

The enum way is not necessarily "unidiomatic Go". Even if you have something that turns data structs into "strategy" objects that know how to render, you need a matching statement, or you need to build something like a "map[Type]Drawer", which is in my opinion less elegant.


Yeah, umm, especially in high-end games, the idea of every object "knowing how to draw itself", in the stereotypical OO way, is completely absurd.


This is both a blessing and a curse for Go. The language touts itself as being easy to pick up, and has a batteries included stdlib.

This allows developers to quickly say "I know Go. Here's what's wrong with Go." since they believe they have learned all there is to know about it. This is good, since that's part of why Go is approachable.

However, this has downsides. As you showed, there are succinct features that can make the difference between "this is a bad thing" and elegant code.


Should the language be cryptic and complex for people to take it a little more seriously?

OK, it seems I'm trying hard to defend Go. I like it, I seldom (but do) miss generics, I miss some Haskell/Rust features and wish it were a little less simple. But I don't understand why there are dozens of similar "things about Go" articles posted every week on HN, all repeating the same stuff.

The one from Evan Miller was both entertaining and in-depth, but that's the exception.


You're clearly know a lot about this topic. Not everyone else is quite as up to speed and the article was a rather interesting view from a learner's perspective. No one expected it to be perfect, and it was particularly well considered considering the experience and time the author had had.

Our comments are ultimately feedback. We're a community and we want constructive feedback to help each other learn. When giving feedback, just remember to be constructive and consider how your tone could be perceived. I'm sure that you had the best intentions but the undertone of the comment read to me (and many others) as very cutting/attacking.


This great comment would be better without the needless insults. Consider omitting them next time.


Your example is not idiomatic Go either, the interface should be named Drawer not Drawable.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: