Hacker News new | ask | show | jobs
by tyrankh 2955 days ago
Preface: vgo _specifically_ calls out the fact that maintainers of libraries have to be backwards-compatible within a major version, and that the onus is on users to put their trust into libraries not to break them appropriately:

> Modules are assumed to follow the import compatibility rule—packages in any newer version should work as well as older ones—so a dependency requirement gives only a minimum version, never a maximum version or a list of incompatible later versions.

(https://research.swtch.com/vgo-mvs)

Back to the article - it seems predicated on this scenario:

> “Our project depends on X@v1.5.0 right now, but it doesn’t work with X@v1.7.0 or newer. We want to be good citizens and adapt, but we just don’t have the bandwidth right now.”

If your deps + your transitive deps for some package are:

- 1.5.0 (you)

- 1.5.1 (some transitive dep)

- 1.4.7 (some transitive dep)

vgo will choose 1.5.1.

However, if your deps for some package are are:

- 1.5.0 (you)

- 1.5.1 (some transitive dep)

- 1.7.3 (some transitive dep)

vgo will choose 1.7.3 and presumedly your app will break.

In other dep managers, you might specify <1.7.0. How would this work? Grab two versions of the package (1.5.1 and 1.7.3), rewrite the import paths of the stuff that requires 1.7.3, and kind of opaquely have two version of the same thing? Or perhaps modify the way the import "xyz" works to be more opaque to solve this problem somehow? There's no nice solution to this.

This seems a fairly reasonable tradeoff; on the upside is a _very_ fast, very simple, and very predictable dependency manager. On the downside is that I have to really think about which libraries I trust not to break me instead of relying on my tool to specify ranges and the like.

Generally though, the ask from vgo is that folks care about backwards compatibility and think about trust, rather than covering up the issue. It's not going to be great for everyone, but I like the straight forwardness of it.

2 comments

> Grab two versions of the package (1.5.1 and 1.7.3), rewrite the import paths of the stuff that requires 1.7.3, and kind of opaquely have two version of the same thing?

Yes, in systems like Cargo you end up with multiple versions of the same package. This typically "just works". It works so well, in fact, that often times people don't even realize they're using multiple versions of the same package, and they want Cargo to report an warning here.

> This seems a fairly reasonable tradeoff; on the upside is a _very_ fast, very simple, and very predictable dependency manager.

For other package managers, speed of the core dependency resolution algorithm has never been a problem for me or anyone else I know.

Go packages can have side effects. For example a global map, background goroutines, shared connection pools, etc. Including multiple versions of the same package would not work for these cases.

Actually this isn't really a go specific thing. If I have a struct defined in a package version 1 that gets an extra field in version 2 how can I possibly reuse that struct between packages.

> Go packages can have side effects. For example a global map, background goroutines, shared connection pools, etc. Including multiple versions of the same package would not work for these cases.

Yes, that's a problem too, but there's a lot of packages which don't have side-effects (I'd expect more of the latter, in fact).

Your point that there needs to be control over packages that should be singletons and/or "public dependencies" is a fair one (it's something that has been considered for cargo for a while, but I'm not sure it's been implemented yet), but allowing multiple versions for packages without side-effects is a strict improvement over the abstraction-breaking "one version globally" for every package.

> If I have a struct defined in a package version 1 that gets an extra field in version 2 how can I possibly reuse that struct between packages.

You can't: they're different types. The underlying assumption of having multiple versions of a package is that imports have unique (name, version), not just name, meaning "a" v1 and "a" v2 are treated as completely different packages, just like "a" v1 and "b" v2.

Because every other dependency management system has realized that nothing about the version number actually tells you anything about compatibility & so they don’t even try.

That vgo encodes semver as sone sort of contract system is crazy pants on a whole new level.

Many package managers interpret semver in a similarly mechanical way, e.g. npm, bundler, cargo (and, I think, elm-package, and I'm sure others too), but they choose different versions based on that information.
And crucially, in these systems you are given an escape hatch (declaring versions that are not ok), which vgo does not support.
I think vgo allows you exclude specific versions in go.mod. It only works for specific versions, not ranges though
Sure they do. What if I do want to share the same struct between packages? I need both of my dependencies to share the same version of their dependant packages.

I don't think I understand what you're getting at. In ruby I can say I depend on a version > 1.2.0 and some other project could use my package and use version 1.2.1, thus altering my dependency. Bundler lets me do that because it makes assumptions about what version numbers mean.

The lock file makes sure this only changes when I want it too, but semver is an integral part of the system

Other dep managers could at least tell you about the conflict. Maybe that's what vgo needs, a way to specify a conflict bound so that any downstream user is notified if the depgraph has conflicts (ofc conflict bounds would only apply after del resolution as simple checks otherwise it wouldn't be MVS)?
> Maybe that's what vgo needs, a way to specify a conflict bound

This is a valid idea; you should make a proposal or issue for it. Alternatively, it seems like something that would be easy to add separately - I personally like the idea of vgo as more of a simple, unix-like building block that you'd build on top of. Adding a small utility CLI checks incompatibilities with `vgo list` + some incompatibilities.txt file would be pretty easy.