NHacker Next
  • new
  • past
  • show
  • ask
  • show
  • jobs
  • submit
Keeping your Go modules compatible (blog.golang.org)
poorman 1386 days ago [-]
> When you run into a case where you want to add a method to an existing interface, you may be able to follow this strategy. Start by creating a new interface with your new method, or identify an existing interface with the new method. Next, identify the relevant functions that need to support it, type check for the second interface, and add code that uses it.

I like this pattern, and I have used it. The issue I have is when I look at the generated godoc, the original interface is in the signature. I don't know the function accepts the new interface as well without either digging into the code or a comment for the function.

anonymoushn 1386 days ago [-]
It seems like this convention invites us to create this sort of issue more times: https://news.ycombinator.com/item?id=6060351
ElCampechano 1386 days ago [-]
How did you solve the pattern of not having generics in the language?
enjoylife 1386 days ago [-]
I use Go daily and one of the aspects which is always a challenge is equality checking. From the post,

> There is one subtle way a new field can break user code unexpectedly. If all the field types in a struct are comparable—meaning values of those types can be compared with == and != and used as a map key—then the overall struct type is comparable too. In this case, adding a new field of uncomparable type will make the overall struct type non-comparable, breaking any code that compares values of that struct type.

From experience, this is non-trivial to avoid in a long lived codebase.

It’s telling that 99% of the codegen tools used in Go, e.g. protobuf, thrift, Gorm, etc. all output an isEqual method of some kind.

kstenerud 1386 days ago [-]
Testing for equality/equivalence is a huge challenge in a language like go. It's bitten me enough times (especially with recursive data) to write a library to tackle the complexity: https://github.com/kstenerud/go-equivalence
jbamsterdam 1386 days ago [-]
I find I rarely need equality checks beyond simple types like ints, strings and times. The big exception to that is tests, for which I recommend cmp (https://pkg.go.dev/github.com/google/go-cmp/cmp).
apta 1386 days ago [-]
Anyone who's worked on a large project and code base will appreciate the features proper mature languages like Java or C# offer.

It's ironic that golang was supposedly designed for "programming in the large", but it's just a very bad experience for non-trivial code bases.

kitd 1386 days ago [-]
Java has an equals() method. What features are you thinking of?
pjmlp 1386 days ago [-]
- Type safe enumerations

- Generics (lets see if flyweight design actually makes it)

- package names that don't depend on source locations

- binary packages for proper encapsulation and even faster builds

- both points together allow for components eco-system to thrive

- more knobs to turn on when performance matters

- a proper graphical debugging experience

kitd 1386 days ago [-]
I was really thinking about the problem described by GP. But in any case:

- Type safe enumerations - Generics (lets see if flyweight design actually makes it)

I'll give you both those. Generics would be the killer feature for me.

- package names that don't depend on source locations

Java package structure mirrors the file system, no?

- binary packages for proper encapsulation and even faster builds

Go package binaries are cached after build. In any case, build speed is rarely an issue for Go.

- both points together allow for components eco-system to thrive

One of the most impressive thing about Go IMO is the speed at which the ecosystem has grown and is growing.

- more knobs to turn on when performance matters

I have found it takes a lot longer to reach the need for performance knobs in Go than in Java.

- a proper graphical debugging experience

Not sure what you mean by "proper" and why it has to be graphical, but VSCode + Golang extension gives me enough insight into what's going on under the covers at debug time, equivalent to eg Eclipse + Java debugger.

Just for the record, I love Java and have been programming in it for over 20 years. It has many great features and the ecosystem is unsurpassed.

Go is a much more practical language though IMHO. The time to value is much shorter. Setting up a module project is one command. Writing & running tests, complete with coverage and performance, is supported out of the box by the basic "go" command. The standard lib is broad and deep. And as mentioned the ecosystem is growing fast.

pjmlp 1386 days ago [-]
> Java package structure mirrors the file system, no?

No, it mirrors the directory structure, from import location, without any reference to DNS servers or source code repositories.

All in all Go is a Java 1.0, with all plus and minus that it entails.

ridv 1386 days ago [-]
Just want to point out that Go can also be used without dealing with DNS servers and repos in a similar way to Java.

The big difference is you have to place the code in the $GOPATH with the right directory structure and disable mod (GO111MODULE=off)

naringas 1387 days ago [-]
there's something that I just don't get about golang modules, I don't know what it is but they don't click with me

it might well be something tacit that golangers learn from each other directly? or otherwise it's so obvious and simple and I just haven't realized?

it feels like it's the kind of thing that just clicks one day possibly after a few years

m0th87 1386 days ago [-]
You're not alone. I've been writing go since before 1.0, and can handle modules/packages in _any other language_ fine, yet go's regularly leaves me stumped.

A little while ago we were hitting yet another issue with dependencies related to version pinning at work. I tried to divine what the disparate versions mean, and came up with this flow chart from a legalistic reading of stack overflow issues [1] and the go modules doc [2]:

  has the package opted into go modules (i.e. is there a go.mod?)
    yes
      does the repository use semver?
        yes
          is the major version <= 1?
            yes
              => normal versioning, e.g. github.com/stretchr/testify v1.3.0
            no
              => when importing, we need add /vN at the end, e.g., import "github.com/my/mod/v4", and in go.mod it will behave like github.com/my/mod/v4 v4.1.0
        no
          => pseudo-versioning will be used, e.g. github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181
    no
      does the repository use semver?
        yes
          is the major version <= 1?
            yes
              => pseudo-versioning will be used, e.g. github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181
            no
              => version will be marked as incompatible, e.g. github.com/zeromq/goczmq v4.1.0+incompatible
        no
          => ???
I still don't know if this is right. But that's not really the point. I think the go authors painted themselves into a corner with such an anemic initial release. Now that they're trying to address shortcomings, the complexity is blowing up to handle the very large/diverse ecosystem. It reminds me a lot of java around 1.4 when they added in generics. In the long-run it was the right move, but man was it painful.

1: https://stackoverflow.com/a/57372286 2: https://github.com/golang/go/wiki/Modules

deathanatos 1386 days ago [-]
I packaged a golang project once. I had to independently discover a lot of that.

I'll also add that fetching a golang package was complicated. As I remember, GitHub seemed to be special cased. Other URLs involved an HTTP request with a ?go-get=1 parameter, and then parsing the returned HTML for certain meta tags¹. (I thought only Python was guilty of that sin!) Some URLs I ended up just re-writing, because gopkg.in packages effectively redirect to GitHub in ways that, if abandoned, allow someone else to intercept[1].

And none of this is documented anywhere particularly coherent. Bits here and there, maybe.

(Why not use go itself? Because this platform's package manager wanted to verify that the software was being built from the correct source, by hashing the inputs. It also, then, fetched those inputs, so that there wasn't some form of bait & switch, and there is no network at build time to further prevent silliness. I considered vendoring, but it isn't clear to me how vendoring works with go modules, since it doesn't seem to take into account the module version? / the path layout only allows for a single version.)

[1]: https://github.com/go-fsnotify/fsnotify

¹some servers didn't even return the right Content-Type, either, but perhaps that's an implementation bug. But I didn't have a spec to know.

ArmandGrillet 1386 days ago [-]
IMO the documentation isn't there yet, it lacks a Q&A webpage or something short and giving the main how-tos in a succinct manner: How to create a module? How to bump a package version? How to use a specific package's branch? What to do if there is a conflict because a package used by another package messed up its compatibility?

The current first Google result when looking for "go modules" is https://blog.golang.org/using-go-modules which is a very verbose post starting with "This post is part 1 in a series of 5 parts". Not very engaging.

In one year I've accumulated a few snippets that I use regularly and get used to `go mod` but it was painful at the beginning. I wish I could have contributed to this topic but Go is most of the time a language that only requires to read pkg.go.dev and the main blog posts to know what's next and this Go modules workflow is, like gopls, something that doesn't currently have a good place to be documented AFAIK.

Edit: Thaxll's comment gives a very nice URL to learn about Go modules, still too much for a beginner IMHO but it has a Q&A part that goes very deep.

Thaxll 1386 days ago [-]
It's pretty clear and well explained there: https://github.com/golang/go/wiki/Modules

On a day to day operation, you need to know 2/3 commands that's it.

- go mod init

- go mod tidy

- go get xxx

bootlooped 1386 days ago [-]
I use `go mod vendor` a lot.
fourseventy 1386 days ago [-]
I agree. I used godep before and I thought it was intuitive and had no issues with it. But I still struggle with gomodules
blondin 1386 days ago [-]
same.

go team dropped the ball on this. i feel a sense of non-consensus among themselves on what modules should have been. and it's frustrating that there is no definite documentation either.

maybe one day...

1386 days ago [-]
kstenerud 1386 days ago [-]
What the go team fails to realize is that they haven't actually reduced complexity in their design of go; they've simply MOVED it. And it's been moved in unintuitive ways as they've painted themselves into corners that should have been avoidable with some careful planning.

We now have:

- magic file names

- magic directory names

- magic comments

- magic env vars

- API design requires gymnastics and juggling since recursive imports are disallowed

- A ton of useful code in the standard library remains unlayered and unexposed for no good reason (pprof, for example)

- Requiring full paths rather than relative imports complicates everything when you're fetching it from a different source location (replace directives and magic ENV vars).

- Slice implementation means that you need to do double pointer storage to keep track of the same slice from multiple places

- Creating arrays with a dynamically defined length is horribly overcomplicated

- Their policy on warnings and instead calling every little thing an error is terrible and wastes a ton of developer time.

- Lack of runtime const value support is just sloppy.

- Why do go source files even need a package declaration? It serves no useful purpose since the compiler already knows enough to spit out an error if you get it wrong.

- Refactoring across packages is nearly impossible to automate because there's no way to declare adherence to an interface.

- No importing of test code from other test code (which means that the import code in the compiler has increased complexity to enforce this silly rule).

- Stack overflows are STILL impossible to track down (after a DECADE!) because it only prints the top of the stack, which is useless in such a case.

- Linting is too opinionated and is all-or-nothing.

------------------

I could go on. I really wanted to like go, and many parts of it do actually feel nice. But even after a decade it's a huge mess. I've spent more time writing workarounds for go than for any other language in my 25 year career. Hell, I even modified the damn compiler out of frustration! [1]

[1] https://github.com/kstenerud/go#the-go-programming-language

dan_hawkins 1386 days ago [-]
> - Why do go source files even need a package declaration? It serves no useful purpose since the compiler already knows enough to spit out an error if you get it wrong.

I know about one use for it: package mypackage_test allows you to exclude test code from builds. I agree with the rest of your points though :)

anonymoushn 1386 days ago [-]
Has something changed such that adding an exported field to a struct is now safe in the case of clients who are using struct embedding? Previously: https://blog.merovius.de/2015/07/29/backwards-compatibility-...
jbamsterdam 1386 days ago [-]
Nothing's changed, and that's still an issue. We didn't address it because the post was long enough, but as Merovius himself says, it's called out in the Go 1 compatibility promise. So the Go standard library doesn't worry about it, and neither should you. It's also extremely rare; I've never seen it in 10 years of Go programming.
opqpo 1387 days ago [-]
Go modules and the entire package management and versioning system is just embarrassingly horrible. It looks like it was designed by people who have no background in software engineering at all. The language as a whole despite being now universally used as the backbone of cloud native, it honestly looks like it's intentionally designed to be horrible and insulting for anyone with sensible mind. Every obvious right decision looks like it's intentionally being replaced by a bad one if it's the right path is easier.
hinkley 1387 days ago [-]
Something I knew starting out, forgot, and learned again, is that there's a huge liability to spending your entire career working for one company.

There will be things they do so well they aren't even on your radar, and you will have a huge blindspot there. And there are things they tolerate that pretty much nobody else will. So you will either suffer in ignorance or learn some nasty, nasty habits.

Without moving you see no diversity, and without seeing diversity you shouldn't be put in charge of building anything for 'everyone'. You are only qualified for niche solutions, which unfortunately outsiders might not clue in on until they've invested heavily in your ideas.

Jtsummers 1386 days ago [-]
In college we referred to this same idea (among my peers who were going on through graduate studies, particularly PhDs) as "academic incest", where you aren't sufficiently exposed to a diverse enough set of ideas and end up too narrowly focused or clouded in your judgment.

Ways to overcome it include: moving (in academics, accepting a fellowship at other institutions; as a professional, change jobs); observation (keeping abreast of the latest trends outside your immediate environment and field); community participation (attending conferences, symposia, and similar things; or even forums like this).

But ultimately it requires a recognition that your views, knowledge, and beliefs are, necessarily, constrained by your experiences and that you sometimes have to seek out novel experiences or the experiences of others to change yourself. If you lack the drive for either, or too great a confidence in your own present state, then you're going to be stuck.

Ericson2314 1387 days ago [-]
That's true, but Google is big and they use many languages, and the amount of ignorance needed to make Go cannot be explained by that alone.
mseepgood 1387 days ago [-]
> Go modules and the entire package management and versioning system is just embarrassingly horrible. It looks like it was designed by people who have no background in software engineering at all.

Go modules is probably the most well-wrought versioning system for a programming language. The amount of thought that went into it is insane.

https://research.swtch.com/vgo

littlestymaar 1386 days ago [-]
It's the most over-though versioning system ever actually. It's a rare mixture of NIH syndrome and obsession to a specific theoretical concern. Yes semver dependency management is NP-hard[1], but lawn mowing is NP-hard too, and yet that doesn't seem to stop anyone else!

[1] in fact it's only NP-hard if you add an optional aditionnal constraint: that every dependency must have a single instance even if it's a transitive dependency from several dependencies with conflicting requirements. If you allow different major version of the same dependency to coexist in the dependency tree, it becomes a straightforward linear problem while still behaving like every single other package manager in existence for the end-user. Now that's a well thought system! (And there at least one language, pretty popular these days on Hacker News, which does exactly this)

closeparen 1386 days ago [-]
Yet asking Glide or Dep to solve semver for a typical medium-sized Go service would peg your CPU fans for an hour or two and usually fail.
plorkyeran 1386 days ago [-]
And ask NPM to install a package and it's able to pick a version of all eight hundred dependencies that package pulls in within a few seconds.
arp242 1386 days ago [-]
The one and only "internet meme" I ever created while I was waiting for dep to finish, and is about dep being slow :-)

https://www.reddit.com/r/golang/comments/7c1dj9/the_real_rea...

Of all the package systems I've worked with, I've had the least about of trouble with Go modules. Some aspects can be a bit confusing and idiosyncratic as first, but overall I found it quite easy to understand and reason about (which is important especially when things go wrong! I still remember my "I can't figure this out, fuck it, I'll rm -rf shit until it works"-Bundler days all too vividly).

LukeShu 1386 days ago [-]
Go does allow coexisting major versions, just not coexisting minor versions.
blondin 1386 days ago [-]
> Go modules is probably the most well-wrought versioning system for a programming language. The amount of thought that went into it is insane.

appreciated, but i share GP's sentiment.

without the uncalled for bashing though. because package management and versioning is hard. for all the backlash they get, package management in python, ruby, php, nodejs, all makes sense to me!

they make so much sense that, in my opinion, that the situation is akin to picking a side and sticking to it. just like you would for your favorite text editor. you perfectly understand what is going on.

go package management on the other hand makes no intuitive sense!

it took like 3, 4, or 5 long blog posts to explain it to us. and all i got from them is how hard package management truly is. i still can't intuitively do it.

i haven't touched php or nodejs in a while but i will be able to get composer or npm or npx (or whatever it is nowadays) to install something for me, locally or globally. not saying it is done right. what i am saying is i get it!

sorry but something is not right with go modules.

jacques_chester 1386 days ago [-]
Go mod is a gigantic pig's breakfast. It would work OK if everyone was on it already, but everyone is not on it already.

If I took a survey of every go.mod file and looked at the versions, more than half would still be that absurd, un-eyeballable, merge-review-hostile, another-arbitrary-c-hacker-mini-language, shas-upon-shas version string that the "insane amount of thought" landed on because it assumes everyone is already conformant. The ugly corner case for "unversioned" dependencies is the main case.

The "insane amount of thought", as is the norm for the Golang team, involved a gymnastic demonstration to evade (1) any serious consideration of prior art in use whatsoever, and (2) entertaining not even the slightest concept of how shitty it would be to impose massive externalities on everyone else.

Even an apologetic tone would have made the pill less bitter. Or better yet, blessing godep. Or copying Maven wholesale. Or Bundler. I don't care, because what happened has vapourised millions of developer hours chasing down completely unnecessary versioning issues.

entha_saava 1386 days ago [-]
> The "insane amount of thought", as is the norm for the Golang team, involved a gymnastic demonstration to evade (1) any serious consideration of prior art in use whatsoever

"We never do what those Java 1xers do, because we are blue collar 0.01xers furiously copy pasting code"

ithkuil 1386 days ago [-]
It's a delicious breakfast but, served for dinner.

I love the mechanics of it. Most of what you describe is the result of having waited ages before implementing it, and the resulting prolonged migration period, exacerbated by the relatively decent (but not perfect) backward compatibility with pre-modules code.

Prior art isn't perfect either: I'm happy somebody tried something different from centralized naming systems or almost-but-not-quite federated artifact collections (such as the various maven repos). There is already a global system to get hold to unique names: the domain name system. For better or worse, that's a solved problem, let's not create new naming arbiters

jacques_chester 1386 days ago [-]
> Most of what you describe is the result of having waited ages before implementing it

Except we already had godep. It worked. It was in widespread use.

> exacerbated by the relatively decent (but not perfect) backward compatibility with pre-modules code.

My experience has been that the backwards compatibility has been a nightmare. It forces itself on any downstream repository, whether or not that repository wishes to participate. Plus it does very poorly on diamond dependencies, giving error messages about as scrutable as the I Ching. If your answer is "they're doing it wrong", take it up with the Kubernetes and go-client maintainers, because I can't change what they are doing.

> There is already a global system to get hold to unique names: the domain name system. For better or worse, that's a solved problem, let's not create new naming arbiters

Versions are not addresses. Addresses are not versions. You may find a version at an address, but addresses are not versions. Docker did the same thing with image references and they too created a pig's breakfast. The difference was that they didn't have prior art to compare to and it was an oversight obvious largely in hindsight. I do not extend the same credit to Russ Cox.

ithkuil 1386 days ago [-]
> Except we already had godep. It worked. It was in widespread use.

> un-eyeballable, merge-review-hostile, another-arbitrary-c-hacker-mini-language, shas-upon-shas version

godep shared the same problem. We can argue that godep was good enough and the Go maintainers handled the situation poorly with this whole "ask community and then ignore the community" trick. But you're focusing on the transition; if this system had been part of Go 1.0, I would have had nothing bad to say about it.

I'm not arguing the the transition process wasn't a mess. I'm arguing that it became a mess precisely because they wanted to attempt backward compat. They could have said "this is go 2; if you want to use your library with go 2 you have to use this standard, tag your release, or upload it to some central index"

Thaxll 1386 days ago [-]
godep was awefuly slow and buggy.
swagonomixxx 1386 days ago [-]
Will have to second this. I didn't find godep more usable than glide, and I find vgo (current Go module implementation) to be far superior to godep.
monkeyfacebag 1386 days ago [-]
This comment would benefit immensely from a single concrete example. As it is, it scans as bluster.
jbamsterdam 1387 days ago [-]
Do you have any specific examples in mind?
candiddevmike 1387 days ago [-]
Reading these blog posts, you get see Go's simplicity start to unravel with modules. Uncomparable structs? Type checking workarounds? You start to wonder if these bandaids would be necessary in a generics world.

For the record, I'm indifferent on the chosen module solution being right or wrong. My ideal module tool is something like npm, but it shows me the impact: diff the module changes, look at my code, and tell me what's impacted by the upgrade. Giving developers more information when upgrading libraries will go a long way towards making "update your libraries" a regular housekeeping task.

mseepgood 1387 days ago [-]
> Reading these blog posts, you get see Go's simplicity start to unravel with modules. Uncomparable structs? Type checking workarounds? You start to wonder if these bandaids would be necessary in a generics world.

Nothing of this has to do with or would be better with generics.

jrockway 1386 days ago [-]
Modules have nothing to do with comparable structs. It's just that making a comparable struct uncomparable could break users of your API, so it's something to be on the lookout for if you value backcompat. (The module system ensures that users will get the version of the code they want; you only have to worry about backwards compatibility if you expect users to upgrade to your new version.)

There are millions of ways to break users with an update that machines can't detect. For example:

Old version:

func Lt(a, b YourType) bool { return a < b }

New version:

func Lt(a, b YourType) bool { return a >= b }

This is not specific to Go.

jbamsterdam 1387 days ago [-]
Despite the title, the problems that this article discusses could have happened even before modules were introduced. Before modules, your only choices were to stay compatible or break users. Modules give us the freedom to make breaking changes in a safe, documented way.
merb 1386 days ago [-]
btw. this blog is about binary/source compatibility, which is a fucking hard problem in nearly EVERY language. (maybe not in javascript tough)
Ericson2314 1387 days ago [-]
Gah in every language I want that interface comparison tool, and I keep on being disappointed.
jbamsterdam 1387 days ago [-]
Until gorelease is ready, you can try apidiff (https://go.googlesource.com/exp/+/refs/heads/master/cmd/apid...).
Ar-Curunir 1386 days ago [-]
rust-semverver does what you want for Rust, I think?
Nemo157 1386 days ago [-]
It would if it worked, but every time I've tried using it I've hit some combination of crashes and weird false positives.
arp242 1386 days ago [-]
"Argumentum ad generics"
Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact
Rendered at 07:55:47 GMT+0000 (Coordinated Universal Time) with Vercel.