Go run
breadchris.com
breadchris.com
Use `go run .` instead - it's shorter and it works with multiple files
I feel like there are two relatively distinct populations of go developer: those who love how easy it is to start (true!) and those who are frustrated by the compromises the language has made to allow for more complex cases (required!). There's also a hidden third population of people who no longer sing the praises of golang as a simple, straightforward language but accept its compromises and write productive code with it. Those people, I think, write fewer viral blog posts.
The problem is many Go tutorials start out by teaching the complex case first and leave the simple case to later (if they cover it at all).
Introducing the concept of "packages", and the fact that the directory is a package, can be deferred until later.
Either could be desirable or undesirable depending on your goals. It's good to be aware of the dynamics so that you can make an informed choice about how to present your code.
The main benefit is that you can easily figure out what is where!
Navigating unfamiliar Go codebases yields very few surprises: things are almost always where I expect to find them, and it's great! This is hardly the case with other languages, where I have to rely on grep or trace function-calls
$ go run ./cmd/pathto/cli(long time I didn't use Go, back then at least, using `.` instead of `./...` could cause subtle issues)
> Potential design based on discussion with proposal review:
> go run [go flags] [single-package-or-*.go-list] [subprocess flags]
before that it was just
> go run [go flags] [*.go-list] [subprocess flags]
go help run
"usage: go run [build flags] [-exec xprog] package [arguments...]Run compiles and runs the named main Go package. Typically the package is specified as a list of .go source files from a single directory, but it may also be an import path, file system path, or pattern matching a single known package, as in 'go run .' or 'go run my/cmd'."
Nothing is said about go run main.go.
Beyond that there's no way to discover what build flags may be needed to give you the binary that the developer intended. Be it tags, ldflags, cgo support.
go.mod
go.sum
library.go // package library
cmd/foo/foo.go // package main
cmd/bar/bar.go // package mainI mean, it kinda is. If you're a Go developer that went further than hello world, you will probably be aware of these two possibilities:
If . contains a "main" package then it's "go run .", otherwise the source for the binaries probably reside in "./cmd/X" and you have to run "go run cmd/X".
You can probably find Go projects that don't follow these rules, but I doubt anyone would want to interact with them.
Bazel does this for _all_ languages. I'm sure most other modern generic build systems do too.
AFAIK "go run" is trivial and for single-file scripts it's fine. But for more complex cases (like the NPM equivalent thing) I actually think it's a bit of a shame that it's even needed. I don't really know why we have per-ecosystem build systems (Maven, Go, cargo, whatever the hell you're supposed to do in Python these days, the nebula of web front-end tooling, etc etc).
Admittedly I do not really know the ins and outs of any of these systems in detail. I'm sure there are some good reasons why they exist under the hood.
- the inventor of new language 'coolang' has a way that they make their project
- it's kinda messy, so they clean it up into a tidy script with a few clear and straightforward commands and/or flags, and give you "cool build," "cool install" for making sure all the necessary dependencies are present, etc
- a community builds around coolang organically
- everybody is so used to running "cool build" that that's just how it's done. New features get added around these conventions
It's cultural, that's all it is. But like all small tight knit communities, it's important to understand the culture of the community in order to engage with it on its own terms. Its just humans being humans.
If coolang decided to try to add coolang support to Bazel instead, they would probably have to learn Java[1]. Current maintainers or contributors to Bazel don't know coolang, and they don't care about it much, especially in the early stage. And maybe coolang developers don't know Java, or even actively hate it with a passion (that's why they were on the market for a new language). And even if some coolang developer decided to contribute to Bazel, the barrier would be much higher: being a mature build system with so many features and different needs, surely working in it is going to be complex; there will be many different concepts, and layers, and compromises, and APIs to work with. So for them it just makes more sense to use coolang so that all coolang developers can contribute to it having a real need for the cool tool to improve.
[1] I know nothing of Bazel. So just bare with the example even if it's technically not correct.
The core of your point is correct: who wants to both support an additional tool chain and an additional language for building things? Terrible sell.
Go itself is a little bit of an edge case because they recommend leaning on Make, but ironically they do not use Make for its intended purpose and all the (actually good) functionality that Make gives you is reimplemented from within the go compiler.
The worst offenders are C and C++ projects. Make? CMake? You’re on your own. During development, it’s so good to be able to just runtime run source like in go and bun.
Or you could just read "Linking a single object file" from the GNU Make manual's catalogue of rules, which describes how to do exactly what you want with the caveat that you still have to run the program after it's built and linked: https://www.gnu.org/software/make/manual/html_node/Catalogue...
You know, like net/http, for example...
I might have misread the docs, but somehow I doubt it.
For everyone else, they want runtime run sourcefile and can then build scripts around that to package it up how they want to. For local development, I shouldn't have to wait 40 minutes for a compile to see a div change.
The toolchains that would benefit from being able to run quickly, compile quickly, are what we want. Having to do 15 steps to get your code on an embedded device is just part of the territory. The rest of us have pipelines.
(Admittedly I have never tried this with a modern build system. My real world approach for that would be a janky phony rule in a Makefile, with a bunch of MAKE_VARS the user has to set on the cmdline/in the environment to set up the toolchain/serial port etc. But in principle I have always believed it should be possible to make this process as easy as compilation)
Why must everything be explicit? Wrap it all up into one var.
cmake -DBUILD_FOR=esp32 .
Then provide your own little runtime.sh.For other languages and build systems, a runtime run sourcefile is the easiest way to get someone going, leave the edge cases out. Just get it running so they can hack on it.
Same for runtime.sh.
This is why the 'runtime run source' is so useful! I'm totally on board with it. I just don't think we need one implementation per ecosystem.
One runner per project (the 'make run' approach) is worse than one per ecosystem, which is in turn worse than just having one for everything!
In Bazel (and, I assume, other similar systems), you can just "bazel run target" and it always works. Doesn't matter what languages the thing is written in.
Make and CMake are certainly not like what. (Yes, you can have a "run" target, but that's not the same thing).
So my minor gripe is that 'runtime run source' does not need to be a per-runtime thing.
The Opensource rulesets are also not fantastic in my opinion compared to the Google ones, so most peoples first impression of the tool is sub-par.
Whereas in the open source there is much more manual setup to get it working smoothly. And the manual setup is much easier in the tool your ecosystem already knows.
So it may not actually be a wonderful system in and of itself (outside of the Google monorepo). My comment was mainly about the principle rather than an endorsement to adopt Bazel!
User expectation's, mainly. In fact, Google didn't even use the go tool internally, which I expect hasn't changed, using Google's build system à la Bazel instead. It was created only for the wider audience.
I don't understand what the problem is here? Every installation of Node comes bundled with npm. If it doesn't, that is a package maintenance problem.
> Fun fact: One of the understated features go run is that it will automatically download any dependencies the code references; how cool is that!
This feels like a massive antipattern. Why is this lauded as a "feature"? Why do I want my build system to automatically reach out to the Internet and download random code, without an explicit request to do so like "npm install"?
This is even more antipattern-ish when you consider that Go dependencies are literally just repos on Github (or possibly on some random git server), instead of a centralized and moderated registry like npmjs.org.
> amazing, for js we not only have npm, yarn, pnpm, and bower (am I missing any?) but we also have completely new runtimes bun and deno.
So it's now considered bad to have multiple implementations of an open standard, compared to the exclusively-Google-developed Go runtime? This sounds akin to arguing in favor of a monopoly over a competitive market with consumer choice.
There is no such safeguard when your dependency system downloads random code from random git repos. Even worse so when this is done automatically, when a developer doesn't expect a command to do so.
If I run a command that depends on a third party library or resource, and I don't have that library, I fully expect it to fail. Is that not basically universal behavior in Unix?
The Go team is providing the following services run by Google: a module mirror for accelerating Go module downloads, an index for discovering new modules, and a global go.sum database for authenticating module content.
Seeing how many CPU cycles have been wasted on autoconf generating code and testing for various ancient/obsolete C compilers and configurations has taught me that yes, it's not a good thing to have multiple slightly incompatible implementations.
It's not random code, it's code you've expressly used.
Node and npm are two commands, and when you go to find packages, you will see people telling you to use pnpm, yarn, or npm. I would expect one tool to do this for me, especially for the most popular language in the world.
> This feels like a massive antipattern. Why is this lauded as a "feature"? Why do I want my build system to automatically reach out to the Internet and download random code without an explicit request, like "npm install"?
https://chat.openai.com/share/8bd82c15-c939-4e82-aad8-086995...
> This is even more antipattern-ish when you consider that Go dependencies are just repoed on GitHub (or possibly on some random git server) instead of a centralized and moderated registry like npmjs.org.
I find this to lend itself to a more decentralized future. I see notable projects owning their code and distributing it positively. You still need the source code for something to run at the end of the day. If you are worried about the code continuing to be there, that is the purpose of a proxy cache, which makes it very easy: https://proxy.golang.org/. Also, the code is distributed on github. So, if github working is a concern, we probably have much bigger problems.
> So it's now considered harmful to have multiple implementations of an open standard, compared to the exclusively Google-developed Go runtime? This sounds akin to arguing for a monopoly over a competitive market with consumer choice.
A hammer looks like a hammer because that is the most effective way to hit a nail. Since I am "building" code, I want my tools to feel as reliable as a hammer. I will not argue that Go is the best language ever invented; I see it as the most accessible language to make things happen fast and reliably until a better one emerges. When that happens, AI-generated refactoring tools will be so good, and Go code is so quickly parseable that I will let it loose in my Go code bases to refactor them into that language.
Does your hammer has built-in car or drone to bring nails from store? No. that's why some people think that it's reasonable to split programs that have different modes if operations.
Your choice, but note that it's not universally accepted true or demand.
Also, a proxy and a package registry are not the same thing.
Not really.
Ubuntu's node comes without npm, and to install the latter it wants to get about a hundred of dependencies. Mind you, this is still one of the most popular distros. Would you call their approach "a problem"?
If you are using an unofficial repository, and that maintainer did not include npm in his Node package, then yes I would consider that a problem.
> to install the latter it wants to get about a hundred of dependencies
Yeah, as software engineers, let's discourage code reuse, shall we?
Node and npm are not trivial pieces of software. I expect it to have lots of dependencies. This is hardly surprising.
[0] https://nodejs.org/en/download/package-manager#debian-and-ub...
But comparing it to JavaScript isn't fair. JavaScript has paid my bills for years, but it's held together by collective hope.
The only thing missing is a decent mobile framework. I'm using Fyne, but it just looks dated. At least for my current app it's functional though.
Go seems too high level for low level work - use Rust, manage your own memory, no garbage collector.
Go also seems too low level for high level work - use TypeScript with all the nifty ES6 features, powerful type system, exceptions, etc..
Where does Go fit in here?
I strongly prefer go error handling compared to a throws-type-error-handling language.
Also, with this comment I hope to get some pushback: I haven't kept up with the latest typescript, python or any other language features. I'm talking from almost a purely ignorant perspective so I hope to learn a bit more on how developing with other languages feels like.
Can't push back there - every other language I'm aware of uses at least one (and often both) of "throwing exceptions" or "returning Result types which either contain your actual data, or an Error", both of which let you just write your logic and wrap it in a single handler rather than repeating `if err != null return _, err` everywhere (or if you _want_ to handle each error individually, you can!)
I've gradually reached the conclusion that Gopher's really just do prefer GoLang's verbose repetitive approach. And, y'know what - good luck to y'all. It's not for me, but I'm trying to get better at just letting people enjoy things :)
Agree re: Results type in Rust and Ocaml, etc. Those are better in my view too. And yes, you can define a Result<any> return type in Typescript as well (and in fact that's what I mostly when I write typescript and works ok) but unlike Rust this is definitely not 'idiomatic typescript/js' and other developers who might not be familiar with Result types will probably initially dislike and then probably dismiss it.
In Go, every call made to a function is 5 statements and lines. Go code tends to bloat up the screen quite a bit and eyes glaze over.
result, err := f()
if err != nil {
return nil, err // I don't want to handle this here but at callers.caller.
}
return resultFurther to what the other replier said (about the ability to bubble-up errors), try-catch also lets you handle multiple errors in one block:
``` try { fileOutput1 = getSomethingFromFileSystem() fileOutput2 = getSomethingElseFromFileSystem() fileOutput3 = ... } catch (FileSystemException e) { // handle } ```
If I understand it correctly, GoLang's idiom would claim that this is a bad thing to do, and each error should be handled individually. Which - sure! That's _usually_ a reasonable, defensible, and safe position. But that means that GoLang's approach is always as verbose as its possible to be, whereas try/catch at least has the _possibility_ to condense handling.
> ...and also much more brittle
Can you be specific about what you mean by "brittle"? To me, it denotes a lack of flexibility - that is, if thing1 changes in an unexpected-but-still-legal way, then thing2 is likely to break. I can't see how that applies to try/catch-vs err-check - in both cases:
* The exception/error is bound to a variable
* (in most well-typed languages) the Type of the exception is checked by the type system, and/or (in every language, inc. GoLang) properties of the exception are checked by code
* Something is done (a standard code action, a return/throw of an exception, or a program termination)
You can write a brittle GoLang check (only checking for, say, `if e.message = "a very specific error message"`), and you can write a very flexible try/catch block (with a fallback `catch (Exception e) {doSomethingGeneric()}` - or, indeed, the _most_ flexible "try-catch" is "don't even catch it, let it bubble-up and let your framework/application handle it")
More of a push forward, really: if error-handling guarantees are what's driving you away from dynamically typed langauges, Go is pretty much the worst place you can land that isn't C. It doesn't make you check nils, it doesn't remind you to check error values from functions that you call only for side effects (though the linter will, admittedly), and it doesn't have sum types so there's semantic ambiguity even in the common case - that is, in `data, err := fn()`, it's common to assume that at most one, and perhaps exactly one, of `data` and `err` will end up non-nil, but that's not a constraint you can express with the type system.
I'd love to have the chance to explore the nuance of what other tradeoffs include going with any other language, but certainly requires more nuance than a deep comment response might trigger.
But just trying my luck, what do you think is worth trading off the more exhaustive error handling? (Regardless on dynamically typed or not)
A lot of this is just syntax, but I've just about come to the conclusion that I'm too stupid to learn it.
Golang is very easy, I can generate small binaries to do cool things without too much code.
Typescript is dragged down by the legacy of JavaScript, things randomly break all the time, configuring babel is the stuff of nightmares.
It took me from a couple years of "I should learn rust" to "I've written some rust and ran rust programs" in a few hours.
Literally everyone comes with their own project setting, they all have to get used to that specific folder structure, or those eslint/prettier settings. And on top of error-handling in JS/TS is miles behind Go's (and that's despite Go's error handling also being clunky and not as elegant as Rust's or ocaml's, but still much much better than JS's). It is _extremely_ easy to mess up a typescript project, it's significantly harder to mess up a go project (although obviously still easily possible :)
Btw. I also don't hate typescript/JS, I think it's a great language that allows for a big variety of expressiveness in entirely different programming domains, I personally use it all the time and enjoy it. I just don't think it's a particularly great language to scale a team with.
Where you move past academic language discussion and start using the tooling. Typescript is a pretty nice language but the tooling around it is practically unusable. It's laughable how bad it is. Outside of browser work, you're going to pick Go – and still would even if they made the language 10x more flawed – over Typescript every time just to not have to deal with that ecosystem.
Granted, people are trying to make it better. Dahl going on his Go kick and wanting to copy its lessons in the Typescript world via Deno has lit a fire, but there is still a lot of work to do.
I work mainly with node/ts and totally agree, maybe just add that by tooling it is whole ecosystem as well. This problem is not visible if you work either with relatively small code base or new code base. But as soon as you have something old and big you'll see where the pain comes from.
Go is probably the most efficient language for 0 -> Production. The standard library has everything you need to build a production backend service. There's zero build system shenanigans. Anyone who's seen a C-like language can start writing mediocre code today, and be pretty well off in 2 weeks.
So far at Notion we're solving all our problems with Typescript/NodeJS, but I'm currently working on a distributed system with Consul that needs to move a lot of bytes in and out of files in somewhat complicated ways, and boy howdy am I feeling the painful performance ceiling of single-core NodeJS, and I'm sure if I sat down to rewrite the performance sensitive part in Go, I'd be done in a few days and it'll do 10x the throughput of the NodeJS service with the same resources.
1. No shared objects between threads. You can share non-resizable contiguous byte arrays (SharedArrayBuffer) but 98% of existing code makes normal objects and arrays, and if you want to send those to another thread, you pay a serialization memcopy round trip (no cast a buffer in this language). This severely limits threading to “shared nothing” style workloads. Can you pass a node HTTP request to another thread? No :(
2. Each thread worker needs to boot up from scratch from its own entry point file. This forces some pretty weird code layout and imposes a big boilerplate overhead as well as runtime overhead. And remember - no sharing! So if your threads need a common resource like a Postgres connection pool, they’re going to create their own copy.
2. You can do this w/o restarting worker each time, which helps. Just keep it alive and submit work to it.
and then coordinate state in parent worker.
No more needs to be said after that.
IMHO it's great for docker-based services. And that's a pretty big marketplace.
You can solve most problems with if-else and loops. This wasn't something I was aware of before Go, but now I see how simple it is and can be.
It strips the problem domain down to its core because you're forced to express the solution in the simplest form it can be. I know a lot of Go haters throw vitriol for exactly this reason (see fasterthanli.me/articles/lies-we-tell-ourselves-to-keep-using-golang), but the truth is simplicity really gets you 80% of the way and most of the time that's enough.
(It's only a subset of the JavaScript ecosystem, but you can import a lot of npms nowadays.)
Although, with slight modification, you can `go run -C ~/that/project/over/there .`
No, you do not. You can just use .mjs extensions for esm. You can also run typescript to transpile your code and then run it with node. You can even use loaders, etc.
Saying you are going to have to use the included package manager in node is probably the weakest argument for using go over node.
Can you run some language superset over go magically without some transpilation? No, you cannot.
You cannot build a argument comparing js to ts vs go, it doesn't follow.
In the 50+ year history of software development I haven't heard of any other software stack has been able to realize this is important. go run is close but it's still 10 times slower, maybe even 100 times slower, depending on if you want to count the git clone and how good you are at typing.
All this plus talk about non-standard JS runtimes like Node, but no mention that this is how browsers have worked almost forever.
At this point, URL imports are actually bad due to the confusion between source and compiled code. Ideally, imports should always point to source code. Bundling / minification should happen at the application level; it's not a library concern.
So in that sense, Go's a lot cleaner since it's always been a compiled language.
Quite frankly, I don't want to automatically fetch dependencies at the same time I am running the code. IMO those should be separate steps, and combining them together in one is not a good idea.
Running the code is a separate step from determining what code I am going to run; the latter includes determining exactly what versions of all dependencies I am going to run. The two should not be combined.
But for that first time, I still want to separate the two steps, for the reasons I've given elsewhere in this discussion.
What's the point in making `go run` error out when it already knows what dependencies to get and how to get them.
No: "go mod download" if I want to update dependencies; then look to see what got updated and how it will affect what I'm doing. Then "go run".
> What's the point in making `go run` error out when it already knows what dependencies to get and how to get them.
Because I don't care what "go run" knows. I care what code is going to run when I say "go run". I want that code to be the code I already know is there and understand. I don't want it to be some new code that "go run" downloads because it sees that an update to a dependency is available. Downloading that update and understanding what effects it has is something that I want to do before "go run", not as part of it.
Why? Node's had built-in support for ES Modules for eons now.
Everything's easy when you stick to default tooling, duh, like `node run.js` or `go run.go`. You'll loose that `go run.go` the moment you need code generation.
Not really.
//go:generate ...
It's pretty handy.And a look at Github shows me Go has a go.mod file. I don't see the point the author wants to make, neither of them affect the build/run command.
$ cat helper.mjs
export const sleep = (dur) => new Promise(resolve => setTimeout(resolve, dur))
$ cat main.mjs
import { sleep } from './helper.mjs'
await sleep(1000)
console.log("Go is great; but weird throwing node under a bus here?")
$ node main.mjs
Go is great; but weird throwing node under a bus here? $ cat main.mjs
import { sleep } from './helper.js'
await sleep(1000)
console.log("Go is great; but weird throwing node under a bus here?")
$ node main.mjs
file://main.mjs:1
import { sleep } from './helper.js'
^^^^^
SyntaxError: Named export 'sleep' not found. The requested module './helper.js' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:import pkg from './helper.js'; const { sleep } = pkg;
'What is CommonJS?'
npm install nanoid Nano ID 5 works only with ESM projects, in tests or Node.js scripts. For CommonJS you need Nano ID 3.x (we still support it):
This whole module bit in node has been a total disaster. Incredibly frustrating
Shout out out to Bun (and Deno too?) for allowing you to treat typescript as an interpreted language. Great for scripting with all the bells and whistles.
(Go is great, just pointing out that running TS does not actually require NPM anymore)
At a surface level, this was partially an intellectually interesting project because it is similar to a language translation project, however instead of parallel sentence pairs, I will probably probably be creating a parallel corpus of "decompiled" C code which will have to be aligned to the original source C code that produced the binary/object file.
Then I realized, the only way I could reasonably build this corpus would be by having some sort automated flow for building arbitrary open source C projects...
Perhaps I will attempt this project with a Go corpus instead.
Though TBF then you have to type ./a.out, and so then you want to do `make && ./a.out` and then...
Shell scripts ...
Was funny when it was diagnosed but not so funny for the time where I was deeply confused why things broke.
npx, tsx, deno, *.mjs and bun make it convenient to run typescript tools.
Bun has recently added a very interesting feature, bun shell: https://bun.sh/blog/the-bun-shell
But anyway, just being rare, so deserves to be ignored?
That, and the ability to cross-compile without installing a cross-toolchain for the target platform. Having spent an inordinate amount of time writing build systems and compiling/distributing cross-toolchains, this is a _huge_ deal.
I do see makefiles periodically like the author notes, but that’s almost always related to secondary build objectives, such as cross-compile or containers etc.
if I refactor some too complicated bash script into a script.go file and then execute it against production DBs/APIs with a `go run`.
go run mvdan.cc/sh/v3/cmd/gosh@latest -c ' go run github.com/mikefarah/yq/v3@latest n foo.bar.hello world | go run github.com/cezarsa/glolcat@latest'https://github.com/mvdan/sh is the repo looks like v3.8.0 was released 2 weeks ago.
$ go install ./...
$ <cmd name> run_%:
cd ./cmd/$* && \
go run .I recently was dealing with some docker containers that we needed to abuse. The app within the containers was not returning helpful errors. One quick script and a go build later I had a portable binary that could return a responsible error message.
npm run dev is what I run all the time
It was never a blocker for me
...are they claiming that you can run Go without installing Go?
"go run" is yet another wonderful feature of an awesome language.
Now, go run.