* not using posix args
* obscure incantations to run tests
* go.mod tooling is completely non-deterministic and hard to use, they should have just left the old-style vendor/ alone (which worked perfectly) and wrapped a git-submodules front-end on top for everyone who was afraid of submodules. Instead they reinvented this arcane new ecosystem.
If you want to rewrite the golang tooling, I'll consult on this for free.
Everyone downvoting obviously doesn't understand the problem.
That said, if it helps people do “their thing” in what they believe is an easier (more straightforward) way, then I welcome the new changes.
It did, but if you recall it came with a lot of "We have no idea why you need this" from Pike and friends. Which, of course, makes sense when you remember that they don't use the go toolchain inside Google. They use Google's toolchain, which already supports things like code generation and build dependency management in a far more elegant way. Had Go not transitioned to a community project, I expect we would have seen the same "We have no idea why you need this" from the Go project as that is another thing already handled by Google's tooling.
The parent's experience comes from similar sized companies as Google who have similar kinds of tooling as Google. His question comes not from a "why would you need this kind of feature?" in concept, but more of a "why would you not use the tooling you already have?" angle. And, to be fair, none of this is needed where you have better tooling, but the better tooling we know tends to require entire teams to maintain it, which is unrealistic for individuals to small organizations. So, this is a pretty good half-measure to allow the rest of us to play the same game in a smaller way.
The digraph problem of build tooling is hardly new, though the ability to checksum all of your build tools and executables and mid-outputs to assure consistency is relatively new to feasibility. Bazel is a heavy instrument and making it work as well as it does was a hard problem even for Google. I don't know anyone making the same investment, and doubt it makes the slightest hint of sense for anyone outside the fortune 500.
`go.sum` is a lock file for the exact versions to use (ensures reproducibility)
`go mod graph` will produce the dependency graph with resolved versions
`go list -deps ./...` will give you all packages used by a module or directory, depending on the args you provide
`go get -u ./...` will update all dependencies to their latest version
Here is a post about Go toolchain reproducibility and verification: https://go.dev/blog/rebuild
You are being downvoted for being wrong and talking about downvoting, which is called out as something not to do in the posting & commenting guidelines
The blog post and design document both authored by Rob Pike at the time[0] contains none of that sentiment. The closest approach comes from the blog post which states:
> Go generate does nothing that couldn’t be done with Make or some other build mechanism, but it comes with the go tool—no extra installation required—and fits nicely into the Go ecosystem.
This, taken alone, would seem to support “we have no idea why you need this,” until you read the hope from the design document:
> It is hoped, however, that it may replace many existing uses of make(1) in the Go repo at least.
These are not words of someone who doesn’t understand why users would need this.
Also, I am at a FAANG and my experience differs from the parent—`go tool` is sorely needed by my teams.
[0] https://go.dev/blog/generate
[1] https://go.googlesource.com/proposal/+/refs/heads/master/des...
Using "go tool" forces you to have a bunch of dependencies in your go.mod that can conflict with your software's real dependency requirements, when there's zero reason those matter. You shouldn't have to care if one of your developer tools depends on a different version of a library than you.
It makes it so the tools themselves also are being run with a version of software they weren't tested with.
If, for example, you used "shell.nix" or a dockerfile with the tool built from source, the tool's dependencies would match it's go.mod.
Now they have to merge with your go.mod...
And then, of course, you _still_ need something like shell.nix or a flox environment (https://flox.dev/) since you need to control the version of go, control the version of non-go tools like "protoc", and so you already have a better solution to downloading and executing a known version of a program in most non-trivial repos.
No, it doesn't. You can use "go tool -modfile=tools.mod mytool" if you want them separate.
However, having multiple tools share a single mod file still proves problematic occasionally, due to incompatible dependencies.
Until that time when they realize everyone else was right, and they add an over simplified, bad solution to their "great" language.
Personally, I think the way PHP handles dependencies is vastly preferable to every other ecosystem I've developed in, for most types of development - but I know its somewhat inflexible approach would be a headache for some small percentage of developers too.
Heh, were the people who made 'go tool' the same people who made Maven? Would make sense :P
The `go tool` stuff has always seemed like a junior engineering hack — literally the wrong tool for the job, yeah sure it gets it working, but other than that it's gross.
And didn't quite understand the euphoria.
I've done the blank import thing before, it was kinda awkward but not _that_ bad.
> user defined tools are compiled each time they are used
Why compile them each time they are used? Assuming you're compiling them from source, shouldn't they be compiled once, and then have the 'go tool' command reuse the same binaries? I don't see why it compiles them at the time you run the tool, rather than when you're installing dependencies. The benchmarks show a significant latency increase. The author also provided a different approach which doesn't seem to have any obvious downsides, besides not sharing dependency versions (which may or may not be a good thing - that's a separate discussion IMO).
From my experience at Google I _know_ this is possible in a Megamonorepo. I have briefly fiddled with Bazel and it seems there's quite a barrier to entry, I dunno if that's just lack of experience but it didn't quite seem ready for small projects.
Maybe Nix is the solution but that has barrier to entry more at the human level - it just seems like a Way of Life that you have to dive all the way into.
Nonetheless, maybe I should try diving into one or both of those tools at some point.
So I think a general solution would work better, and not be limited to Go. There are plenty of tools in this space to choose from: mise, devenv, Nix, Hermit, etc.
but are there really that many tools you need in a go project not written in go?
In Python’s uv, the pyproject.toml has separate sections for dev and prod dependencies. Then uv generates a single lock file where you can specify whether to install dev or prod deps.
But what happens if I run ‘go run’ or ‘go build’? Will the tools get into the final artifact?
I know Python still doesn’t solve the issue where a tool can depend on a different version of a library than the main project. But this approach in Go doesn’t seem to fix it either. If your tool needs an older version of a library, the single go.mod file forces the entire project to use the older version, even if the project needs—or can only support—a newer version of the dependency.
No. The binary size is related to the number of dependencies you use in each main package (and the dependencies they use, etc). It does not matter how many dependencies you have in your go.mod.
Plus, `go tool <tool-name>` is slower than `./bin/<tool-name>`. Not to mention, it doesn’t resolve the issue where tools might use a different version of a dependency than the app.
If you want, you can have multiple ".mod" files and set "-modifle=dev-env.mod" every time you run "go" binary with "run" or "build" command. For example, you can take what @mseepgood mentioned:
> go tool -modfile=tools.mod mytool
Plus, in last versions of the Go we have workspaces [0][1]. It is yet another way to easily switch between various environments or having isolated modules in the monorepo.
1. it is single tree
2. BUT tools will not propagate through the dependency tree downstream due to go module pruning
check this comment: https://github.com/golang/go/issues/48429#issuecomment-26184...
official docs: https://tip.golang.org/doc/modules/managing-dependencies#too...
> Due to module pruning, when you depend on a module that itself has a tool dependency, requirements that exist just to satisfy that tool dependency do not usually become requirements of your module.
I really dislike that now I'm going to have two problems, managing other tools installed through a makefile, e.g. lint, and managing tools "installed" through go.mod, e.g. mocks generators, stringify, etc.
I feel like this is not a net negative on the ecosystem again. Each release Golang team adds thing to manage and makes it harder to interact with other codebases. In this case, each company will have to decide if they want to use "go tool" and when to use it. Each time I clone an open source repo I'm going to have to check how they manage their tools.
old design give people option to use `tools.go` approach, or other, or nothing at all. now they are enforcing this `tools.go` standard. Go looks to be moving into very restrictive territories.
what about surveying opposing views? what about people who did not use `tools.go`
what is going on in Google, Go team?
basically it go tool relies heavily on go module pruning, so transitive dependencies from tools are not propagated downstream.
also, official docs say this: https://tip.golang.org/doc/modules/managing-dependencies#too...
> Due to module pruning, when you depend on a module that itself has a tool dependency, requirements that exist just to satisfy that tool dependency do not usually become requirements of your module.
after digging deeper, it is alright
Another great blog post [2] covers performance issues with go tool
``` //go:build tools
package tools
import ( _ "github.com/xxx/yyy" ... ) ```
Being able to run tools directly with go generate run [1] already works well enough and I frankly don't need see any benefits compared to it in this new approach.
you can still leave comments in discussion issue: https://github.com/golang/go/issues/48429
When I use `go tool`, it uses whatever I have in go.mod; and, in the opposit way, it will update my go.mod for no real reason.
But some people do this right now with tools.go, so... whatever, it's a better version of tools.go pattern. And I can still do it my preffered way with `go install <tool>@version` in makefile. So, eh.
Likewise, the standard library NodeJS http stack will not be as performant as a performance optimized alternative.
That said, if raw performance is your primary concern, neither Go nor NodeJS will be good enough. There's many more factors to consider.
> There's many more factors to consider.
Erlang / Elixir?
Go being garbage collected and still beating v8 is one hell of an achievement.
If you need faster Go Http you're using the wrong tool.
I'm not sure how GC is relevant here at all given that JS is also a garbage-collected language.
We went back on forth on this a lot, but it boiled down to wanting only one dependency graph per module instead of two. This simplifies things like security scanners, and other workflows that analyze your dependencies.
A `// tool` comment would be a nice addition, it's probably not impossible to add, but the code is quite fiddly.
Luckily for library authors, although it does impact version selection for projects who use your module; those projects do not get `// indirect` lines in their go.mod because those packages are not required when building their module.
I'm not a library author and I try to be careful about what dependencies I introduce to my projects (including indirect dependencies). On one project, switching to `go tool` makes my go.mod go from 93 lines to 247 (excluding the tools themselves) - this makes it infeasible to manually review.
If I'm only using a single feature of a multi-purpose tool for example, does it matter to me that some unrelated dependency of theirs has a security issue?
Did you consider having tool be an alias for indirect? That would have kept a single dependency graph per module, while still enabling one reading one’s go.mod by hand rather than using ‘go mod’ to know where each dependency came from and why?
I know, a random drive-by forum post is not the same as a technical design …
I don't understand your comment when you should know copying proven features is a good thing.
Yeah I want none of that. I'll stick with my makefiles and a dedicated "internal/tools" module. Tools routinely force upgrades that break other things, and allowing that is a feature.
you got to have isolation of artefact and tools around to work with it.
it is bonkers to start versioning tools used to build project mixed with artefact dependencies itself. should we include version of VSCode used to type code? how about transitive dependencies of VSCode? how about OS itself to edit files? how about version of LLM model that generated some of this code? where does this stop?
The main argument for not overly genericizing things is that you can deliver a better user experience through domain-specific code.
For Bazel and buck2 specifically, they require a total commitment to it, which implies ongoing maintenance work. I also think the fact that they don't have open governance is a hindrance. Google's and Meta's internal monorepos make certain tradeoffs that don't quite work in a more distributed model.
Bazel is also in Java I believe, which is a bit unfortunate due to process startup times. On my machine, `time bazelisk --help` takes over 0.75 seconds to run, compared to `time go --help` which is 0.003 seconds and `time cargo --help` which is 0.02 seconds. (This doesn't apply to buck2, which is in Rust.)
Running bazel outside of a bazel workspace is not a major use-case that needs to be fixed.
Because being cross-language makes them inherit all of the complexity of the worst languages they support.
The infinite flexibility required to accommodate everyone keeps costing you at every step.
You need to learn a tool that is more powerful than your language requires, and pay the cost of more abstraction layers than you need.
Then you have to work with snowflake projects that are all different in arbitrary ways, because the everything-agnostic tool didn't impose any conventions or constraints.
The vague do-it-all build systems make everything more complicated than necessary. Their "simple" components are either a mere execution primitive that make handling different platforms/versions/configurations your problem, or are macros/magic/plugins that are a fractal of a build system written inside a build system, with more custom complexity underneath.
OTOH a language-specific build system knows exactly what that language needs, and doesn't need to support more. It can include specific solutions and workarounds for its target environments, out of the box, because it knows what it's building and what platforms it supports. It can use conventions and defaults of its language to do most things without configuration. General build tools need build scripts written, debugged, and tweaked endlessly.
A single-language build tool can support just one standard project structure and have all projects and dependencies follow it. That makes it easier to work on other projects, and easier to write tooling that works with all of them. All because focused build system doesn't accommodate all the custom legacy projects of all languages.
You don't realize how much of a skill-and-effort black hole build scripts are is until you use a language where a build command just builds it.
For vendored open source projects that build with random other tools (CMake, Nix, custom Makefile) it's a pain but the fact that it's generally possible to get them building with Blaze at all says something...
Yes, the monorepo makes all of this dramatically easier. I can consider "one-build-tool-to-rule-them-all isn't really practical outside of a monorepo" as a valid argument, although it remains to be proven. But "you fundamentally need a build tool per language" doesn't hold any water for me.
> That makes it easier to work on other projects, and easier to write tooling that works with all of them.
But... this is my whole point. Only if those projects are in the same language as yours! I can see how maybe that's valid in some domains where there's probably a lot of people who can just do almost everything on JS/TS, maybe Java has a similar domain. But for most of us switching between Go/Cargo/CMake etc is a huge pain.
Oh btw, there's also Meson. That's very cross-language while also seeming extremely simple to use. But it doesn't seem to deliver a very full-featured experience.
If your "one build system to rule them all" was built in, say, Ruby, the Python ecosystem won't want to use it. No Python evangelist wants to tell users that step 1 of getting up and running with Python is "Install Ruby".
So you tend to get a lot of wheel reinvention across ecosystems.
I don't necessarily think it's a bad thing. Yes, it's a lot of redundant work. But it's also an opportunity to shed historical baggage and learn from previous mistakes. Compare, for example, how beloved Rust's cargo ecosystem is compared the ongoing mess that is package management in Python.
A fresh start can be valuable, and not having a monoculture can be helpful for rapid evolution.
True, but the Python community does seem to be coalescing around tools like UV and Ruff, written in Rust. Presumably that’s more acceptable because it’s a compiled language, so they tell users to “install UV” not “install Rust”.
It's been such a frustration for me that I started writing my own as a side project a year or two ago, based on a using a standardized filesystem structure for packages instead of a manifest or configuration language. By leaning into the filesystem heavily, you can avoid a lot of language lock-in and complexity that comes with other tools. And with fingerprint-based addressing for packages and files, it's quite fast. Incremental rebuild checks for my projects with hundreds of packages take only 200-300ms on my low-end laptop with an Intel N200 and mid-tier SSD.
It's an early stage project and the documentation needs some work, but if you're interested: https://github.com/somesocks/dryad https://somesocks.github.io/dryad/
One other alternative I know of that's multi-language is Pants(https://www.pantsbuild.org/), which has support for packages in several languages, and an "ad-hoc" mode which lets you build packages with a custom tool if it isn't officially supported. They've added support for quite a few new tools/languages lately, and seem to be very much an active project.
I think you can fix these issues by using a package manager around Bazel. Conda is my preferred choice because it is in the top tier for adoption, cross platform support, and supported more locked down use cases like going through mirrors, not having root, not controlling file paths, etc. What Bazel gets from this is a generic solution for package management with better version solving for build rules, source dependencies and binary dependencies. By sourcing binary deps from conda forge, you get a midpoint between deep investment into Bazel and binaries with unknown provenance which allows you to incrementally move to source as appropriate.
Additional notes: some requirements limit utility and approach being partial support of a platform. If you require root on Linux, wsl on Windows, have frequent compilation breakage on darwin, or neglect Windows file paths, your cross platform support is partial in my book.
Use of Java for Bazel and Python for conda might be regrettable, but not bad enough to warrant moving down the list of adoption and in my experience there is vastly more Bazel out there than Buck or other competitors. Similarly, you want to see some adoption from Haskell, Rust, Julia, Golang, Python, C++, etc.
JavaScript is thorny. You really don't want to have to deal with multiple versions of the same library with compiled languages, but you have to with JavaScript. I haven't seen too much demand for JavaScript bindings to C++ wrappers around a Rust core that uses C core libraries, but I do see that for Python bindings.
Rust handles this fine by unifying up to semver compatibility -- diamond dependency hell is an artifact of the lack of namespacing in many older languages.
I've been working on an alternate build tool Mill (https://www.mill-build.org) tries to provide the 90% of Bazel that people need at 10% the complexity cost. From a greenfield perspective a lot of work to try and catch up to Bazel's cross-language support and community. I think we can eventually get there, but it will be a long slog
https://gist.github.com/terabyte/15a2d3d407285b8b5a0a7964dd6...
Blending those dependencies already causes somewhat frequent problems for library owners / for users of libraries that do this. Encouraging it is not what I would consider beneficial.
I know Python since version 1.6, and this has never been a thing until Rust.
Same applies to the RIIR going on JavaScript side.
Including tools that were already written in compiled languages, but of course weren't Rust, or had an idea to make a startup around them.
A bit like C ignoring previous experience in safe systems, getting lucky with UNIX's adoption, and we are still trying to fix that to this day.
I also see this as sugar for `go build` or even `go run`. Or as something way easier than the `go generate` + `//go:generate go run` hack. So we can look at this as a simple refinement for existing practices.
That sounds like a non-starter. You almost never want to to unintentionally 'upgrade' to the next 'major' ver. There's also occasionally broken or hacked/compromised minor vers.
This is one of the main advantages of using `go tool` over the "hope that contributors to have the right version installed" approach. As the version of the tool required by the project evolves, it continues to work.
Interestingly, when I was first working on the proposal, `go run` deliberately did not cache the built binary. That meant that `go tool` was much faster because it only had to do the check instead of re-running the `link` step. In Go 1.24 that was changed (both to support `go tool`, but also for some other work they are planning) so this advantage of `go tool` is not needed anymore.
So if a build system works great for your mixed C++ projects, your build system is taking on the maximum complexity to deal with it, and that's the complexity I don't want in non-C++ projects.
When I work with pure-JS projects, or pure-Go projects, or pure-Rust projects, I don't need any of this. npm, go, and rust/cargo packages are uniform, and trivial to build with their built-in basic tools when they don't have C/C++ dependencies.
I'm saying that using it for separate C++ and Go projects is extremely convenient and ergonomic.
The issue I was referring to is that in Javascript, you can write code which uses multiple versions of the same library which are mutually incompatible. Since they're mutually incompatible, no sat-solve or unifyer is going to help you. You must permit multiple versions of the same library in the same environment. So far, my approach of ignoring some Javascript libraries has worked for my backend development. :)
But you can use two versions of the same library in your project; I've done it by giving one of them a different name.
The trouble is that MVS will happen across all of your dependencies, including direct and other tool dependencies. If everything very strictly followed Go's own versioning guidelines, then this would be OK since any breaking change would be forced off into a separate module identity. However, even Google's own modules don't always follow this rule, so in reality it's just kind of unrealistic.
You don't need something huge like golangci-lint to run into problems. It's just easier to see it happen because the large number of dependencies makes it a lot more likely.
It already works, but it's work in progress. Happy to get feedback
Having the wrong version installed between collaborators is problematic as then they may get different results and spend time wondering why.
Works across branches and projects.
The functionality of `go tool` seems to build on the existing `go run` and the latter already uses the same package compilation cache as `go build`. Subsequent invocations of `go run X` were notably faster than the first invocation long before 1.24. However, it seems that the final executable was never cached before, but now it will be as of 1.24. This benefits both `go run` and `go tool`.
However, this raises the question: do the times reported in the blog reflect the benefit of executable caching, or were they collected before that feature was implemented (and/or is it not working properly)?
Great question! I wasn't aware of the 1.24 optimization when I first wrote the post, but was interested to dig into this deeper. I updated the post: https://blog.howardjohn.info/posts/go-tools-command/#digging....
The tl;dr is...: 1. The new caching impacts the new `go tool` and the existing `go run`. 2. It has a massive benefit. 3. `go tool` is a bit faster than `go run` due to skipping some module resolution phases. 4. Caching is still (relatively) slow for large packages
It still has a slow startup time, bazel just works around that by using a persistent daemon, so that it is relatively fast after as long as the daemon is running.
Do you encounter cache invalidation bugs with daemonization often? I've had pretty bad experiences with daemonized dev tools in the past.
If you try to run two Bazel invocations in parallel in the same workspace, one waits for the other to be done.
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 run
Easy enough to stuff in a Makefile or whatever.Even better in Go 1.24 since according to this article the invocations of go run will also be cached (rather than just utilizing the compilation cache.) So there shouldn't be much of an advantage to pre-emptively installing binaries anymore versus just go running them.
How is anyone supposed to know whether there's an issue or not? To simplify things, if you use the tool and the dependency belongs to the tool, then the issue can affect you. Anything more advanced than that requires analyzing the code.
now, do you care about some development tool you're running locally has a security issue? if yes, you needed to update anyway, if not, nothing changes.
I'd also note the distinction between `go run example.com/foo@latest` (which as you note must do some network calls to determine the latest version of example.com/foo) and simple `go run example.com/foo` (no @) which will just use the version of foo that's in go.mod -- presumably `go tool foo` is closer to the latter.
The sibling suggests this may still be an issue. I'm not surprised—cache invalidation is very difficult to solve, and conservative approximations like tearing down the whole process each time tend to be quite effective.
Big fan of Dagger over this go tool thing
I generally loath the use of comments for things other than comments
I think some of the euphoria around tracking tooling in the repo is "yes, now I can run a command in the repo and it's as if I spun up a docker container with the CI locally, but it's just regular software running on my machine!" This is a huge improvement if you're used to the minutes-or-hours-long CI cycle being your only interaction with the "real environment."
The reductio ad absurdum that you describe is basically "a snapshot of the definitions of all the container images in our CI pipeline." It's not a ridiculous example, it's how many large projects run.
my key concern whether tools to build project have to be in the same pool as project itself (that may or may not use tools to build/edit/maintain/debug it).
The tradeoff with this approach is that you lose any sort of agnosticism when you drop into the language specific tooling. So now if you work at a corporation and have to deal with multiple toolchains every engineer now needs to learn and work with new build tooling X times for each supported language. This always happens to some extent - there’s always going to be some things that use the language’s specific task runner constructs - but keeping that minimal is usually a good idea in this scenario.
Your complaint feels to me that it is about poorly implemented CI systems that heavily leverage container based workflows (of which there are many in the wild). If implemented properly with caching, really the main overhead you are paying in these types of setups is the virtualization overhead (on macs) and the cold start time for the engine. For most people and in most cases neither will make a significant difference in the wall clock time of their loop, comparatively.
you got to draw a line somewhere.
in my opinion, "if dependency code is not linked nor compiled-into nor copied as a source (e.g. model weights, or other artefacts) then it must not be included into dependency tree of project source code"
that still means, you are free to track versions/hashes/etc. of tools and their dependencies. just do it separately.
Does everyone need to use the same IDE? Obviously not. Same C++ compiler? Ideally yes. (And yes you can do that in some cases, e.g. Bazel and its ilk allow you to vendor compilers.)
linters, formatters, reproducible codegen should be tracked. their output is deterministic and you want to enforce that in CI in case people forget. the rest doesn't really affect the code (well windows OS and their CRLF do but git has eol attributes to control that).
This is why we pin versions. Go tool is common sense, allowing for any old tool version in the build chain invites failure.
It will still add the dependencies of those tools as indirect dependencies to your go.mod file, that is what's being discussed.
There is a lot of merit to this statement, as applied to `go tool` usage and to security scanning. Just went through a big security vendor analysis and POCs. In the middle I saw Filippo Valsorda post [1] about false positives from the one stop shops, while govulncheck (language specific) did not have them. At the same time, there was one vendor who did not false positive with the reachability checks on vulns. While not always as good, one-stop-shops also add value by removing a lot of similar / duplicated work. Tradeoffs and such...
[1] https://bsky.app/profile/filippo.abyssdomain.expert/post/3ld...
again, v1.24 seems to be okay here. go mod pruning should keep nodes in tree clear from polluting each other.
In other words, no need to mix "dependencies" and "dev dependencies" together.
From my experience, Go's dependency management is far better than anything else I've worked with (across languages including Java, Scala, Javascript/Typescript, Python).
Your criticism is perhaps relevant in relation to language features (though I would disagree with your position) but has no basis in relation to tooling and the standard library, both of which are best in class.
Could you elucidate which of the 'mentioned issues' you think are present for Go (in relation to tooling) that do not apply to Rust/cargo? Is your critique based solely on the new `go tool` command or more widespread? And are you aware that the parent criticism is at least partially misguided given it is possible to store tooling in a separate .mod file or simply ignore this feature altogether?
You seem to only use languages with bad dependency management. Which sounds tongue in cheek, but it's true. These are the languages (along with Go) where people hate the dependency management solutions.
Having sad that, I don't really know anyone with significant Go experience who dislikes its dependency management - most critiques are now quite out of date/predate current solutions by several years.
Finally, I'm not really sure which language you are proposing has better dependency management than Go - whether in relation to security, avoiding the 'diamond' problem, simplicity, etc. Rustaceans like to critique Go (no idea if you are one but my experience is that criticism is often from this crown) but I'm not sure how, from my own Rust experience/knowledge, it is actually better. In fact, I think there are several grounds to think that it is worse (even if still very solid compared to some of the aforementioned languages).
Python isn't too bad now-a-days with poetry although the repo spec could use more constraints
Anything is better than npm, with one function per package.
Python, thankfully I only need batteries for OS scripting.
I would rather scratch my eyes out than use Go for the kind of code I write day-to-day, but Go looks amazing for the kinds of things where you can achieve high levels of boringness, which is what we should all be striving for.
I worked with C++/ruby/python/js/java and C#, go mod is superior to all them.
The one that is similar is Cargo ( rust ).
Of course, the problem with that was that it was pretty much impossible to version properly, causing breakages and such.
at least Go doesn't have virtualenvs
That said, I still strongly dislike that the Go ecosystem conflates a library's repo URL with it's in-language URI. Having `github.com` everywhere in your sourcecode just ignores other use-cases for dependency management, like running an artifactory in an enterprise setting. My point being: there's still room for improvement.
The fact that each library and its dependencies gets compiled separately adds quite a lot in build time depending how many crates you reference. But you usually don‘t fight with dependency version issues. The only thing which is not expressed in crates is the minimum rust version needed. This is a pain point when you want or need to stay on a specific toolchain version. Because transient dependencies are defined by default like „major.minor.patch“ without any locking, cargo will pull the latest compatible version during an update or fresh checkout (means anything that is still compatible in terms of semantic versioning; e.g 1.1.0 means resolve a version that is >= 1.1.0 && < 2.0.0) And because toolchain version updates usually happen as minor updates it happens that a build suddenly fails after an update. Hope this makes sense.