Per the post, it sounds like this is most effective in closed-ecosystem internal monorepo-like contexts where an organisation has control over every instance of client code & can `go fix` all of the call sites to completely eradicate all usage of a deprecated APIs:
> For many years now, our Google colleagues on the teams supporting Java, Kotlin, and C++ have been using source-level inliner tools like this. To date, these tools have eliminated millions of calls to deprecated functions in Google’s code base. Users simply add the directives, and wait. During the night, robots quietly prepare, test, and submit batches of code changes across a monorepo of billions of lines of code. If all goes well, by the morning the old code is no longer in use and can be safely deleted. Go’s inliner is a relative newcomer, but it has already been used to prepare more than 18,000 changelists to Google’s monorepo.
It could still have some incremental benefit for public APIs where client code is not under centralised control, but would not allow deprecated APIs to be removed without breakage.
It makes those breakages less painful. A project can eventually remove a deprecated API after notifying other projects to run `go fix`. And when projects ignore that advice (some always will), they can revert to a previous working version, run `go fix`, and then upgrade, without spending time in the code identifying how to replace each removed API.
And for those projects that routinely update and run `go fix`, they'll never notice the removal of deprecated code. Given the other benefits of `go fix`, switching to easier to read methods, and leveraging more efficient methods, in addition to security fixes that come with regular updates, this should be the workflow for most maintained projects.
//go:fix is something understood by a particular implementation of Go. Another implementation could implement Go without implementing support for //go:fix and it would be a fully compliant implementation of Go, the language.
If they made it part of the syntax, that would require other implementations to implement it.
The reason it feels like a kludge is that "comments" are normally understood to be non-impactful. Is a source transformation that removes all comments valid? If comments have no impact per the spec, yes. But that's not the case here.
In practice comments in go are defined to be able to carry semantic meaning extensibly. Whether they're safe to ignore depends on what meaning is given to the directives, e.g. conditional compilation directives.
I keep being impressed at subtle but meaningful things that Go does right.
Just like some other famous languages of the authors.
const x = `This whole thing is
a
//go:generate worse-is-better
multiline
string literal`
A lot of people in this discussion are beating up Go for using syntactical comments for directives, but in reality the implementation is even less principled than that! package main
//go:fix inline
func handle() {
recover()
}
func foo() {
handle()
}
func main() {
defer foo()
panic("bye")
}There was even a more upvoted post between your triple dupe and this https://news.ycombinator.com/item?id=47347322 #scp
These //go.* commands always remind me of this:
"""
//GO.SYSIN DD *
DOO DAH
DOO DAH
"""
(Why yes, that is IBM System 360 JCL from circa 1975. Why do you ask?)
For other things, like `//go:noinline`, this is fair criticism. `//go:fix inline` is quite different in every way.
(My personal theory is that early go had a somewhat misguided idea of simplicity, and preferred overloading existing concepts with special cases over introducing new keywords. Capitalization for visibility is another example of that.)
By making them comments, Go subtly signals that these are exceptional, making them less prominent and harder to abuse.
Yes, maybe some code uses recover() to check if its being called as a panic handler, and perhaps `go fix` should add a check for this ("error: function to be inlined calls recover()"), but this isn't a particularly common footgun.
This is an impossible task. For a library function, you can't know whether or not the function is defer called.
Maybe this is not an important problem. But it would be better if the blog article mentions this.
It is just a demo.
package main
import "unsafe"
//go:fix inline
func foo[T any]() {
var t T
_ = 1 / unsafe.Sizeof(t)
}
func main() {
foo[struct{}]()
}
Go is a language full of details: https://go101.org/details-and-tips/101.html package main
type T = [8]byte
var a T
//go:fix inline
func foo() T {
return T{}
}
func main() {
if foo() == a {
}
}
filed: https://github.com/golang/go/issues/78170 and https://github.com/golang/go/issues/78169 package main
//go:fix inline
func foo[T [8]byte | [4]uint16]() {
var v T
var n byte = 1 << len(v) >> len(v)
if n == 0 {
println("T is [8]byte")
} else {
println("T is [4]uint16]")
}
}
func main() {
foo[[8]byte]()
}> Go designers distinguish between Go language as defined by Go spec and implementation details. > //go:fix is something understood by a particular implementation of Go. Another implementation could implement Go without implementing support for //go:fix and it would be a fully compliant implementation of Go, the language. > > If they made it part of the syntax, that would require other implementations to implement it.
...I'm not sure I buy that argument TBH.
"If they made it part of the syntax, that would require other implementations to implement it." ... I mean, so what? Has golang stopped ading new features to the spec? If not (which I guess so), then how is this any different? Unless you have freezed the language, this reasoning doesn't make sense to me.
The upside of that particular syntax is that only the parser used by tools needs to understand directives. All other parser implementations can be blissfully unaware, negating the need for special no-ops. The downside is...?
(Though for the record, talking about alternative implementations when discussing Go is kind of a funny joke.)
But to be fair to alternative toolchains, TinyGo and TamaGo are also a thing.
It's certainly done in many places. JsDoc is the biggest example I can think of. But they're all walking the line of "this doesn't have an impact, except when it does".
It being done by the language owners just makes them the ones walking the line.
https://go.dev/blog/inliner#example-fixing-api-design-flaws
So these comments carry more weight than how those comment annotations might be consumed by optional tools for other languages.
For most of the listed examples, I think the corresponding C annotation would have been "[[deprecated]]", which has been added to the syntax as of C23.
That's why you find it in the comments. That is where tools have found a place to add their own syntax without breaking the Go code.
Absolutely you can do the same in Java. It exists to the exact same degree as it does in Go. I expect it isn't done as often in the Java world because it is much harder to parse Java code so the tools don't get built.
This is not inlining in the compiler. It's a directive to a source transformation (refactoring) tool. So yes, this has no impact on the code. It will do things if you run `go fix` on your codebase, otherwise it won't.
The issue isn't that this approach is incorrect, it's that it feels out of place. A comment should be a comment, nothing more. When comments start carrying executable meaning or structured directives, they stop serving their primary purpose.
It also becomes difficult to represent anything moderately complex in a clear way. Once the structure grows beyond something trivial, readability suffers quickly.
To me, it ends up feeling like command-line arguments.. technically workable, but messy and hard to reason about. Just look at something like "ffmpeg" arguments.. and then compare that to defining the same configuration through a structured format like Yaml or Json. The latter is simply clearer and easier to maintain.
It's not wrong, but, it doesn't feel right.
It sounds like you are talking about cgo. I think you have a stronger case there, but it is much the same situation: It's conceptually a third-party add-on that the Go language doesn't know anything about. "Cgo is not Go"[1]
I mean, if you really did have your own business logic language that you needed to include in Go code, where else would you put it if not in the "comments"? You, a random third-party, won't be able to add syntax to Go, so you cannot reasonably consider that to be an option. What syntax could Go add that is flexible enough to allow anyone to add anything they need, but that doesn't end up being comments by another token?
> A comment should be a comment, nothing more.
It's not really a comment, though. It is a directive. A comment is structured like `// Who the hell wrote this crap?`, while this is structured like `//tool:name args`.
I think what you are saying is that you don't like overloaded syntax, which is fair, but Go overloads syntax in several places so this is not a unique case. Besides, most languages end up with overloaded syntax in practice, so it isn't even something unique to Go.
To be clear, this isn't a feature of Go. There is nothing in the spec about it. It is what outside tools have hacked on top of Go because it doesn't offer that infinitely flexible feature mentioned above. However, it remains to be seen how you add infinitely flexible syntax without it turning into just being comments again.
If that is indeed the case, I believe it's fair. Im not into language/compiler design etc., but if I have to take a guess, this is where metaprogramming would have helped, right? Like the ones provided by zig or rust?
Things like "//go:embed" and "//go:build" very much do change the semantics of source code.
The comments above 'import "C"' containing C function definitions and imports change the compilation of go source code.
The "//go" comments contain a mix of ones that must be respected to compile, to being optional, to being entirely ignorable (like generate and fix).
It is infinitely flexible for any kind of tool you can imagine, so it is quite likely that someone has created a tool to use in a place where metaprogramming could have been used instead. But I am not sure if it helps globally. The very directive in question, `//go:fix inline`, is a directive for a tool that refactors code — changing the files stored on disk to meet modern idioms. I don't think you would want to use metaprogramming for that, would you? It is not something you want to happen at compile time or run time. It is something you run only when you are ready to modernize your codebase (which, in some cases, might be never).
In practice, the Go language developers carved syntax out of comments, so that a comment is "anything that starts with //, unless the next characters are go:"
If it would make you happier you can imagine this is part of the spec. It wouldn't change much if it was.
Why would millions of programs becoming out of date with the spec make me happy. There is value in the language maintainers and go programmers talking about the same object. I don't disagree that '// ' is standard Go style (and more readable), but it would break all the code that uses //comments /// ////.
I DO agree that it wouldn't change much if by 'it' you mean the go language and it's tooling, a proper spec does prevent arbitrary change. But it should have been added at least 5 years ago.
There was no such statement. "In practice" clearly indicates the contrary about it being "strict" and certainly encompasses the possibility that it only developed over time.