Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Best practices for a new Go developer (medium.com/indianguru)
153 points by torrance on Sept 2, 2015 | hide | past | favorite | 90 comments


    > You will realize eventually that what you first thought
    > was worth criticizing was actually a deep work of
    > genius.
Funny, I found the more I used it the less I liked it.

    > I’ve seen a lot of criticism of Go’s “shortcomings”
    > from people who are true experts in many languages
    > other than Go.
Cool, normally I just call those people "experts".

    > I can’t recall similar criticism from someone who’s
    > worked with Go at a very deep level for a year or two.
You would do well to understand the meaning of the term "survivorship bias".


I completely agree - the more I used it the less I liked it. I really enjoyed its implicit simplicity at first. The code I wrote had purpose and was clear, consice, and easy to understand. However, as the codebase grew larger I really ran into the issues more people talk about. Specifically lack of generics. I was using reflection to simulate this, however my performance was terrible on large sets of data. I had to end up relying on a code generator, which seems to be the direction Go is heading.

Edit: Package versioning was another pain point, as someone mentioned below. Their built in package manager is almost too simple. As with anything simple, it's great at first but not so great when more complexities arise.


If you are using reflection, unsafe or even interface{} heavily, you almost certainly are not writing idiomatic Go. This will doom you to frustration and a poor experience overall.


The package versioning issue has been solved in 1.5 for most of the popular use cases with the vendor experiment. Works just fine for me!


I hadn't heard about the vendor experiment. Is it now possible to point at individual tags/branches of a remote repository?


The new "vendor" system is basically that the Go compiler will look in vendor/ for your import before trying to traverse the GOPATH. This means you can manually download and freeze dependencies there. However, Go leaves the management of this vendor directory to you or some hypothetical helper tool — or with git submodules, for that matter. "go get", for example, still works just like before (ie., stores stuff in $GOPATH/src).


Not directly related, but there's one tweet I like:

"No no no no. The voice of newbies is one of your biggest assets. Why don't they understand? What is the confusion?" John Resig

https://twitter.com/jeresig/status/584030561313943552


The thing about Go is that some parts, like channels and goroutines (also some of the library designs and tooling), are so addictive that pretty soon it gets hard to imagine ever having to live without them.

But I don't think everyone using Go really believes or supports this "but in practice it's not a problem and if it's a problem then the problem is YOU!" kind of response to every criticism.

We live in a world of trade-offs. It's disingenuous to act like that wasn't the case.


The thing about Go is that some parts, like channels and goroutines

I have written a fair share of Go now and have only rarely used channels or goroutines. In practice, channels are only a good model for a subset of concurrency scenarios and they come with problems.

Also, those criticizing Go are right (of course). No amount of Go usage will alleviate its shortcomings. The lack of parametric polymorphism is incredibly annoying, error handling is mediocre (Rust handles this much better), and the lack of support for even simple abstractions (algebraic data types) is jarring.

Nonetheless, I continue to use Go, because they also got a lot of things right: compile times, tooling, a very-well documented standard library, good compatibility between 1.x releases, a UNIX feel, low-barrier entry for other contributors to Go projects, and a thriving ecosystem for a relatively young language. (Of course, the JVM and Java do as well on these points, except for UNIX-feel.)


Where goroutines have helped me a lot is with networking code. The part of the system I'm writing in Go controls several instances of three different kinds of networked services, plus one command line tool.

There are hundereds of tasks that request access to these services. The job of my Go code is to schedule, rate limit and coordinate access by these tasks to the network services.

I did parts of it in C++ initially (because other parts of the system are written in C++) using queues and timers (could have done the same in Java) and then I rewrote it in node.js but wasn't happy with the asychronous design or with my productivity.

So I think the reason why goroutines and channels have helped me so much is specific to this kind of project. Go's other language features (or lack thereof) wouldn't have enticed me into taking a closer look. But now that I have taken a closer look, I do like the library and tools a lot as well.

To sum it up, if a program is basically a ton of loops doing blocking reads and writes that depend on each other, then goroutines and channels make the code look very similar to the idea. Mental load drops. Life is easier.


"like channels and goroutines"

Channels and goroutines advertised as the poster child of Go language features for simplifying concurrency since the beginning. When I tried to use them I was surprised how difficult actually it is to use them correctly, so many subtleties to deal with.

Yes they are addictive, but I suspect beginners really are not using them correctly.


Beginners don't remain beginners forever. My yardstick for whether or not I like a particular approach is looking at old code that I have written months ago. The more quickly I can get back into it without fear of breaking things, the better the design is.

By that admittedly subjective standard, goroutines and channels have been a big success (compared to asynchronous code).


By that admittedly subjective standard, goroutines and channels have been a big success (compared to asynchronous code).

Well, your barrier is also very low :). Try e.g. Erlang's actor model.


What puts me off Erlang is its dynamic typing and lack of speed for algorithmic code. I'm just not smart enough for dynamically typed languages it seems (even though I do like some of them -- Python).


> The thing about Go is that some parts, like channels and goroutines (also some of the library designs and tooling), are so addictive that pretty soon it gets hard to imagine ever having to live without them.

Honestly, I'm fine with actors.

Go is a good language but its for, frankly, good for a very narrow set of applications [cli; microservices]. Outside of those two applications, I find myself never wanting to use Go, honestly.


What's with the condescending tone?


Can't speak for peteretep but here's my guess...

If you look at the quotes peteretep pasted, those "I-have-wisdom-that-you-don't" type of remarks can be interpreted as condescension. Therefore, it's human nature to respond to condescension with more condescension.

To be clear, the blog author (Satish Talim) didn't write those quotes -- he's quoting Baron Schwartz.


The quote he was commenting on had a very condescending tone too:

" Do not complain about something you think is missing, wrong, or stupid. You are ignorant and new to the language, no matter how much of an expert you are on programming in general."


I actually think it's pretty good advice. If you haven't used a language [1] before, park the opinion and get to work learning it.

[1] or database or framework or whatever...


I think it's a canary. How many languages do you hear this said about? I've only heard it in regards to Go which is fairly pedestrian (read: looks and acts something like C / Java) as far as these things go.

More exotic languages, which take the uninitiated longer to acclimate to, don't seem to be trying to tell people they're not allowed to think deeper about and question the design of the language. Good criticisms of Go were made on day 1 and are being made to this day, Rob Pike's ego notwithstanding.

At the end of the day, if I see a problem with a developing language I am interested in, I'm going to report it. If I'm met with: "I don't know you but I'll assume you don't know what you're talking about" that's a second problem and I'm not about to double-down on it.


"I think it's a canary. How many languages do you hear this said about?"

In general, I see "This new language I'm learning sucks because it needs to work more like the language I know best" for all kinds of languages. "Python needs to use braces instead of indentation." "$X needs to handle errors more like $Y." (lots of things can be filled in there!) Complaints about the dynamic/optional/static typing in a language. "Haskell may be pretty but isolating IO effects like that is impractical, it ought to work more like most languages."

No, it's not even remotely isolated to Go.


FYI the quote above refers to the develeoper/community response to criticisms/ideas (eg. see the comment I replied to), not the criticisms/ideas offered by newcomers.


Is that really a general problem in the Go community?

I ask because I've seen people explaining the tradeoffs Go makes pretty clearly online without any rudeness or condescending attitude.


I don't know what it's like now. Rob Pike in particular made a lot of noise like this early on. There was a lot of parrotting of that, so it was hard to have a serious conversation. It seems like it's still getting play but I don't know how widespread that is today.


Yeah I agree, but to my mind its good advice delivered in a very condescending style.


I don't think it's intended. I gather you refer to 'ignorant'(?) but it's innocent enough in meaning.


> Because Go gives us interfaces and closures we can write much more elegant, generic APIs with a flavor similar to Ruby or Lisp and this is the direction the language naturally wants us to take. Personally I like to use the empty interface for plumbing and only pin things down to specific interfaces or concrete types where I need to for performance or correctness.

That's a lot like using opaque pointers in C. What is it about Go that makes people assume it's shortcomings are beautiful designs?

I learned yesterday that `x == nil` can return false even if x is nil so long as x is an interface type. But it depends on whether x is actually nil or a nil value with a specific type.

(╯°□°)╯︵ ┻━┻

My other pet peeve is that a method with a non-pointer receiver that tries to modify the receiver object will silently drop those modifications on the ground, because the object is copied. Which makes some sense, except that Go likes to convert to pointer receivers automatically, so the caller can't tell that anything is wrong. The only difference is one character in the method definition. Everyone I know hits this bug at some point and loses half an hour before they learn to look for it. You could almost say "all method receivers must be pointers" except that you need to refer to interface types without the pointer.

(╯°□°)╯︵ ┻━┻


Go's simplicity is merely syntactical. The semantics can be subtle and not entirely aligned with intuition honed after x years of working with C-like language X.

This is an observation and not a critique.

> I learned yesterday that `x == nil` can return false even if x is nil so long as ...

In other words:

http://play.golang.org/p/YV5ylbQN4j

Thanks for the heads-up! That was news to me.


In the playground you have there, the string `v` is implicitly initialized to the empty string, which is not equal to nil. I'm referring to a different condition, where two different values are explicitly set to nil. Check this out: https://play.golang.org/p/qMmg7yOAKO


Yes this is what trips many people up.

One reason `(* T)(nil) != nil` is because a `(* T)(nil)` can still have pointer methods called on it, and it can still work fine (as long as the receiver is never dereferenced, of course): https://play.golang.org/p/rup5cqapEU


Actually, the issue mentioned by the OP is covered here:

http://golang.org/doc/faq#nil_error

Basically, if you pass 'nil' of one type into an argument which accepts an interface of another type, the nil is no longer nil, but an interface wrapper around a nil (which is not nil).

So:

    var buf *bufio.Reader // a nil value
if this is passed into a function with a signature of

    func foo(buf io.Reader)
then when in function foo:

    foo(buf) // valid, and buf is nil here

    //inside foo:

    if buf != nil { // but buf is not nil here, so this is true
        buf.Read() // Panics because nil has no function Read.


The post you were replying to was talking about whether the behavior is intuitive, not whether it is documented.


I'm confused, how is that different from

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

where calling on the nil value of T seems to work.


When does it make sense to compare a string with nil?


It doesn't but the function admits a T. It actually makes perfect sense, it is just a bit subtle.


That's called using opaque pointers. It's pretty much what you do in C.

???

The empty interface is effectively dynamic typing, relying on runtime introspection. It does not hide any struct members (unless you make them private), you can simply enumerate them.

Opaque pointers in C provide a way to enforce static typing without exposing struct members and are effectively a method for information hiding.

So, I am wondering why you said that.

My other pet peeve is that a method with a non-pointer receiver that tries to modify the receiver object will silently drop those modifications on the ground, because the object is copied.

I agree. In fact, it is seldom that I actually want the non-pointer variant. If I don't modify the object, just give me a const modifier so that I can indicate that.

(Or reasoned from outside Go: the receiver should IMO always be passed by reference.)


You're right. I just toned down my language. What I meant to say is that doing away with type safety is an old idea, and its downsides are old too. You end up with code that's harder to read, because you can't tell what the types are supposed to be, and you run into bugs that show up far away from your mistakes.


I agree. Just wanted to check if I missed something :) (which happens often enough ;)).


Everything in Go is passed by value (i.e. copied), even the pointers. The sooner you will understand this, the less problems you will have.


That's true of nearly every language. "Passed by reference" is almost non-existant in reality, there's only "Pass by value copy" and "pass by copy of the reference" in most cases. The nature of structured programming and the way parameters come in to functions all but forces this; if you have a real function call (that is, not inlined), the reference has to be a copy.

There's a few interesting exceptions to this, but they're very much exceptions.


The part that confuses me is that a lot of pointers are taken implicitly. Method receivers depend on the method definition, and maps (and interfaces?) are always pointers. So it can be hard for me to tell the difference between pass-by-value and pass-pointer-by-value.


The confusing thing is that those copies are wrapped in interface wrapper types when passed into a function which uses interfaces in its function signature.


I'm not interested in C++, not using it, do not care about it. So, I am neither visiting HN links that are about C++ nor taking part in discussions related to C++.

And I'm really curious what drives people in this thread that are coming to say they do not want to use Go, do not like it, and stuff like that?


Go's surge in popularity — and with it, the surge of evangelism — compels people to take a stand, for better or worse. (If Go weren't popular, nobody would bother.)

I suspect it's also partly due to Go's curious mix of maturity and immaturity, in an overall promising package with lots of momentum. Here's a young language that on the one hand is productive to work with and simple to understand, yet so obstinately awkward and inelegant in several other respects, and represented by a team of designers who aren't always saying what many want to hear.

It's clear that Go was, first and foremost, designed for Google and their need for a natively-compiled, concurrent language that steers the user to a very specific path, largely defined by limitations. Its approach prevents language-theoretic cleverness that can complicate a codebase (think Boost or the STL), and it prevents a lot of foot-shooting and optimistic overdesign that newbies are prone to.

But while that's wonderful for Google, that's also a frustrating to developers who don't need this invisible hand hobbling them, and sees Go's potential subverted by the stringent design principles. I admit I'm one of those frustrated by Go's type system, for example, or its heavy-handed approach to error handling.

Part of the frustration is the lack of a clear alternative: Nimrod doesn't have the mindshare, Rust is overly complex and slow to compile, Elixir doesn't have the performance, C++ is, well, C++. Go, for better or worse, fills a kind of sweet spot that, unfortunately, also has a dash of sour. And that kind of frustration easily leads to debate whenever Go comes up.


Lack of alternative++. Python is too dynamic and slow, gradual typing will help but it's still slooooow. For web work, PHP and Hack are popular but often very amateur, node.js works best with microservices and can quickly become difficult to manage. Clojure, Scala, and Java suffer from idiosyncrasies around the JVM. Clojure's lispy syntax can be a drag, Scala gets very complex very quickly, and Java is oh-so-verbose.

C# is about as close as it gets to the sweet spot in the middle, IMO. But .net not being a first class citizen on all platforms kills that option. Yikes.


Agreed on all points. C# does indeed look like it hits the sweet spot, at least in terms of feature set, but I wish it were wholly independent of .NET. Also, certain ugly Windowsisms have snuck into the language, such as CapitalizedMethodNames (but snakeCaseVariableNames, for some reason).

A whittled-down, cleaned up Scala without the JVM might also have hit that sweet spot.

I'm hoping Swift will be a contender once it's open-sourced and becomes multiplatform.


> Rust is overly complex and slow to compile

The compilation speed will be improved, and Rust does more checks during compilation than Go. Regarding Rust's complexity. Go is must easier to start with, but I heard (anecdotally) that when your honeymoon with Go is over you may find yourself bored with repeating, copy-paste and the lack of abstractions in Go. That's the price you pay for simplicity.

> Elixir doesn't have the performance

it depends what kind of application you write.


Rust will improve, of course, but as far as I'm concerned, it's not there yet (type system seems unfinished, lifetime management introduces some really crufty syntax, etc.).

More importantly, its "advanced" nature automatically relegates it to the same land as Haskell, Ocaml or even Lisp, where it's harder to hire.

Elixir/Erlang scales better, but it's a language where you will struggle the moment you need a bit of single-core performance — which you sometimes do, even in a nicely distributed app.


>And I'm really curious what drives people in this thread that are coming to say they do not want to use Go, do not like it, and stuff like that?

Last year I wrote this article about why I didn't like Go very much. It was pretty well received here on HN, which is hopefully indicative of it being a good explanation.

http://yager.io/programming/go.html


Probably because of the relative evangelism levels. I don't see many threads on hacker news talking up the virtues of C++.


Learn about stack versus heap, and recognize that Go treats the stack differently from other languages.

Note that the Go language spec does not even contain the terms 'stack' and 'heap. Whether something is stack or heap-allocated isn't always clear. E.g. consider:

    foo := &Foo{} // or: new(Foo)
This could be stack or heap-allocated (in the most popular Go implementation) depending on whether the compiler thinks it escapes or not.


> Learn about stack versus heap, and recognize that Go treats the stack differently from other languages.

Yeah, this comment strikes me as deeply confused. Go does not give you precise control over what goes on the stack versus the heap: it's even in the FAQ.

There's a common misconception that stack vs. heap and value types vs. reference types represent the same distinction; they do not. For example, note that it is possible in unmanaged languages to have dangling references into the stack.


Unless I'm mistaken it is technically true though, AFAIK other languages fall in two broad groups

* heap-based languages (vast majority of the high-level ones), everything is treated as heap-allocated, may be stack-allocated as an optimisation (while ensuring/conserving heap semantics)[0]

* stack/heap languages, strict semantic separation between heap allocation and stack allocation, the developer decides whether values get heap-allocated or stack-allocated

Go has the semantics separation of the latter (whether something is heap-allocated or stack-allocated had strong semantic significance and is not dependent on the thing's type) but the control of the former (the runtime decides and doesn't tell you)

[0] a subset of these languages may have "value types" which have stack allocation semantics, that's linked to the type itself and the semantics are stable


To use your set of categories, Go is a fully heap-based language. Everything is semantically on the heap. Stack allocation semantics have to do with when the object is destroyed, which Go does not have any way to control in the language. Value vs. reference types are totally orthogonal. To see how this is true, try creating a value type (such as a struct or an array) in Go and capture a reference to it in an (escaping) closure to defeat the escape analysis optimization. The object will definitely be allocated on the heap.


The issue I was referring to is is if you don't

> capture a reference to it in an (escaping) closure to defeat the escape analysis optimization.

the object absolutely doesn't behave as a heap-allocated value. So everything is not semantically on the heap.


Three points:

1. There are many other things that can cause escape analysis to fail. You can also capture references to a value in an opaque function, send it over a channel, etc. etc.

2. There is no semantic difference between stack and heap (modulo when finalizers run). The only thing the language says is that object is guaranteed to be destroyed when it is no longer referenced. The compiler can fulfill this obligation sometimes through stack obligations, but that's done through a set of heuristics. The language says nothing about this (and the FAQ states this quite explicitly).

3. Java does the same thing. The HotSpot JVM will use escape analysis to perform SROA (which is an important optimization). Note that escape analysis is independent of value vs. reference types; Java has much fewer value types, but it can still do escape analysis. This is because value/reference and stack/heap are orthogonal.


I was hoping this article would have more substantive advice for new Go developers. It seemed to mostly have very general advice that applies to most other languages like: "Don't try and write language X in language Y. Keep complexity down by not over using complex language features."

I'm writing my current hobby project, a podcast fetcher, as my first project in Go.

The project has been going generally well but there have been a few annoyances so far:

* Why are you not able to easily version git dependencies? Go's solution to this problem is to tell you to create an entirely new git repository for each major version. Really? If they didn't want to go full blown dependency versioning with something like CocoaPods, they could at least let you specify a git branch or tag.

* The db.Sql abstraction does not support multiple result sets. Therefore database drivers, like the popular mysql driver, don't support multiple result sets. This really limits the kinds of stored procedures you can call.

* The debugger support is bad. I have to fall back to using print statements for most of my debugging.


> Why are you not able to easily version git dependencies? Go's solution to this problem is to tell you to create an entirely new git repository for each major version. Really? If they didn't want to go full blown dependency versioning with something like CocoaPods, they could at least let you specify a git branch or tag.

Take a look at gopkg.in[1]. This is a service that makes branches in other repositories go-gettable. Eg. go getting gopkg.in/foo/bar/v5 will download branch v5 from github.com/foo/bar. This seems to me to be a way of dealing reasonaly well with the issue -- the go get tool itself doesn't need to understand anything at all here.

[1] http://labix.org/gopkg.in


A nasty workaround for a problem which should not exist in the first place. Ditto dependencies on private repositories.


> A nasty workaround for a problem which should not exist in the first place.

In Go I believe they use the term "idiomatic." ;-)


I personally don't see having the version in the package address particularly more nasty than specifying it in the project file, as in Maven, which I have been dealing with lately. One problem I imagine is that when migrating to a new version of a library, the address has to be updated in each file the library is used in, with potentially catastrophic consequences if any one file is forgotten. A grep seems wise in that case.

Would you argue that there are more robust solutions in the Haskell ecosystem? What scheme do you prefer?


I don't think the issue was with specifying the version in the package address. It is that you need to use a 3rd party, remote service, to redirect your requests to the appropriate git repo and branch.

This is because Go doesn't support specifying branches/tags itself when specifying a git dependency.

This workaround is contingent on gopkg.in people keeping their service maintained and running. If they went down, all your dependencies would break.


"You can make it work, but there is a reason why the Go team chose not to implement that. And, no, it’s not that they are lazy."

One of the many wonderful comments in there.

Those annoyances you listed aren't Go's fault. It's yours.


I was expecting an empty page with a huge "don't" in it. But instead there was interview excerpts from Go developers and a ambiguous list of "best practices".It would be more meaningful if this was backed with some actual practices with some code examples.

I just don't see what people think others would understand by teleologic statements like: "write Go the way it wants to be written"


> I just don't see what people think others would understand by teleologic statements like: "write Go the way it wants to be written"

well, this is a very general advice, approx. implying : "don't force your previous language's world view while programming in Go".

f.e. if you are coming to Go from say Java, where everything is a class and where hierarchies abound, when you try to write a 'javaesque' go, you would be fighting against the language.

however, embracing a 'goesque' worldview of composition+interfaces as primary vehicle for program design, would/should make the overall experience quite a pleasant one.


> I just don't see what people think others would understand by teleologic statements like: "write Go the way it wants to be written"

To me it means "Read what the creators thought about when creating Go and how they view it, and do the same". But really that applies to any craft that exists: instead of trying to apply and adapt what you already know, clean your mind and start from scratch.


Wait, the advice is "ignore your past knowledge and experience and start from nothing" ? That strikes me as terrible advice.


More like "Don't try to shoehorn your existing knowledge into that thing you are trying to learn and use; it may or may not fit but isn't supposed to". That absolutely doesn't prevent you to use your knowledge as stepping stones to understand the new thing.


> Wait, the advice is "ignore your past knowledge and experience and start from nothing" ? That strikes me as terrible advice.

well, with knowledge i hope there is wisdom as well to apply it in a manner befitting the context.


> I was expecting an empty page with a huge "don't" in it.

Why?


> I resisted the recommended workspace configuration, as described in How to Write Go Code. Don’t bother, especially in the beginning

I've had the same dev folder structure across platforms, languages, jobs and decades. I basically had to abandon that structure when starting Go. I fought & fought and at the end of the day it is just easier to use their expected workflow. It was (is?) galling but it was the only way to stop fighting the tools and get work done.


The intellij go plugin is your friend. Per project go-paths. It's also a good use of make.


So, if Go is as bad as the comments so far make it out to be, what are some alternative languages? Specifically, a compiled language that can be deployed without worrying about dependencies. That's the feature that has had me looking into learning Go. I want to be able to just copy one file to my server and run it, no need to install anything extra on the server to make my program work.

Actually, can Go even do that? I think it can...


"So, if Go is as bad as the comments so far make it out to be, what are some alternative languages?"

While a good question anyhow, it's not as bad as the comments make it out to be. Anything can be made to look bad by only looking at the negatives, anything can be made to look good by only looking at the positives. And the balance shifts depending on what sort of application you're writing. There's a reason that Go has seen a lot of success writing network servers. There's a reason why Go has no penetration into the scientific computing community, and I tend to warn away anybody even thinking about it.

In particular, a lot of the people screaming about Go do not consider some of the positives. For instance, thanks to the implicit satisfaction of interfaces, I find it one of the easiest mainstream imperative languages to still create a separation between IO and pure code, by wrapping all IO behind an interfaced object, one that I may not even have to create (i.e., Files already implement io.Reader and io.Writer, io.Reader & io.Writer also already have several test implementations available in the stdlib and it's easy to adapt them to a few more), which then allows me to write some really good testing code, almost as good as Haskell. (In fact, swapping in alternate implementations is probably easier than in Haskell.) This can be done in other languages, but it often involves jumping through hoops to create your own objects that mirror other object's methods so they can implement an interface or something; in Go it's trivially easy. Between that and the way errors are handled, I find it relatively easy to write very bullet-proof code.

And while I also consider it annoying that Go lacks half of generics (interfaces are actually half of generics, but it's missing generic types), there are also often ways of spelling your APIs so it matters less. My code actually doesn't end up with very many interface{}s in it, and many of the ones that do are "real", in that the code in question really doesn't care what's there.

If you don't put those positives onto the balance, you don't get a proper view.

It doesn't help that, frankly, this was not a very good article and I strongly agree it has tone issues. This brought out a lot of people who might otherwise have just clicked over without commenting.


> For instance, thanks to the implicit satisfaction of interfaces, I find it one of the easiest mainstream imperative languages to still create a separation between IO and pure code, by wrapping all IO behind an interfaced object, one that I may not even have to create (i.e., Files already implement io.Reader and io.Writer, io.Reader & io.Writer also already have several test implementations available in the stdlib and it's easy to adapt them to a few more), which then allows me to write some really good testing code, almost as good as Haskell. (In fact, swapping in alternate implementations is probably easier than in Haskell.)

I'm confused. How does not having to write "implements Reader, Writer" (25 characters to type) have anything to do with purity and IO effects?

> interfaces are actually half of generics

Interfaces aren't generics at all. Java had interfaces and Object as a supertype before it had generics, and it still had zero support for generics.

I do agree with you that Go has maybe 50% of the use cases for generics covered, but not because it has interfaces. Those don't count. It's because it has maps and growable arrays built in.


"Interfaces aren't generics at all."

It's a definition game. One component of "generic" is "generic algorithm". Go has that covered; the Sort package provides a "generic" algorithm via an interface specification. A lot of C++ template code is built around implementing generic algorithms at compile time rather than Go's run time, for instance.

If you choose to call that "not generics", that's a valid choice of definition, and certainly makes sense in a Rust context, but it's not universally valid.

For context, I'm not trying to bend a definition to "defend Go"... I generally have a low opinion of software engineer's ability to create universal definitions of terms that apply across all languages, and I observe that pretty much any term you can imagine varies across language communities. I'm not the one creating terminology vagueness. And I have observed that every time someone contradicts me and insists some term really is rigorously defined and agreed to by all major language communities, you can count on two or three disagreements from not-me in the replies...


You can add methods on a type you don't control, without casting/subclassing that type. - the original interface does not have to even know that your interface exists.


> Anything can be made to look bad by only looking at the negatives, anything can be made to look good by only looking at the positives.

And then you have the faux-"fair and balanced" posts about Go by jerf, which always ends up favouring Go for some inexplicable reason.


I like it, because I'm writing network servers for the most part. Don't use it for scientific, numeric, or desktop GUI work. You can find posts on /r/golang where I actively discourage that. The further you get from a network server, the worse off you are.

How much more balanced do you want? If "balanced" looks like advocacy, well, maybe the problem is the community has overshot into the trough of disillusionment: https://en.wikipedia.org/wiki/Hype_cycle

If the community was over-praising Go... no, actually, when the community was over-praising Go, you know, that thing that caused the backlash we're in now... I was more negative. It wasn't the Best Language Ever. It's also not the Worst Language Ever.

And I'm generally against seeing "Library X, BUT IN $X!" on the front page of HN for all values of $X, but, that's a separate discussion.


Haskell is my preferred statically-compiled-but-with-coroutines-and-channels language. It's harder to learn and the tooling is not as good as Go's, but its type system is far more powerful and useful. You can build useful generic abstractions over all types of channels and use them in every context. In Go you either have to use interface{} and pay the perf hit or write a code generator.


> Specifically, a compiled language that can be deployed without worrying about dependencies. That's the feature that has had me looking into learning Go.

Any language with a compiler for native code. All of them support static linking, that is nothing special in the Go toolchain.


It's been a few years since my last C++ class, and I haven't done any compiled programming since. So I either completely forgot about static linking, or it just didn't get covered.

Why have I seen several go apps that use static linking, but every C or C++ app I see uses dynamic linking?

A quick google makes me think that the main downsides to static linking are resources and upstream bugs. Those issues would apply to go the same way they'd apply to C. Right? So why does it seem (from my limited experience) that go uses static linking more than other languages do?


The authors are very vocal against dynamic linking.

http://harmful.cat-v.org/software/dynamic-linking/

Modern applications use dynamic linking most of the time, because despite the possible headache with versions, it offers a much more flexible architecture.


You mean any compiled language with static linking? Don't they all support that?

I mean you can even get the jvm/java apps bundled as a single binary if you really want to.


Statically linked binaries is not a new thing. You can do that on many languages.


C++.


its not all go specific, despite the comment about 'C style' heap allocation and pointer usage, you will find your C code will get better if you do not do this as well. the heap is a last resort, not the first.


I found that comment strange as well - I consider "put everything on the heap" to be more of the default attitude in dynamic languages (Python, Ruby) and in the statically typed managed languages (Java, C#). In C and C++, I default to the stack, and I don't think I'm alone.


Yeah, that was odd, I wonder if it was some kind of elaborate typo. Heap allocation is much worse in C than in Go because you're responsible for freeing everything you heap allocate.

There are only a few good reasons to heap allocate in C:

You're writing a library and want to be able to add fields to your data structures without breaking ABI compatibility.

You need to return dynamically sized chunk of data, whose length can't be known to the caller ahead of time, like a string. Still consider returning it by writing to a statically sized buffer passed as an argument, piecewise, as in read().

You need a growable buffer. Still consider allocating a reasonable maximum sized buffer and BOUNDS CHECKING.

Some library requires you to dynamically allocate to use its API. Oh well.

You want to maintain API consistency and that requires dynamically allocating something that doesn't strictly need to be.

Don't be dogmatic, but stack allocation, and occaisionally static allocation are much more maintainable than dynamic allocation.


I take it these are the idiomatic best practices.




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

Search: