it doesn't appear to truly be zero cost if you log variables that can't be eliminated. The only way (I believe) to implement zero cost is some type of macro system which go does not support.

> it doesn't appear to truly be zero cost if you log variables that can't be eliminated.

I'd say it is fair to call it zero cost, if the costs you are seeing are due to the way you are using it. If the values being logged are values you are already computing and storing, constants, or some mix of the two (concatenated via its printf function), by my understanding (caveat: I've never actually used Go myself) all the logging code should be stripped out as dead code in the link stage.

Obviously if you are performing extra computations with intermediate values to produce the log messages, your code there might produce something that is not reliably detected as eliminable dead code so there would be cost there.

> The only way (I believe) to implement zero cost is some type of macro system which go does not support.

That looks to be effectively what it is doing, just at the link stage instead of in a preprocessor. Where C & friends would drop the code inside #ifdef when the relevant value is not set (so it won't be compiled at all) this should throw it out later in the process if DLG_STACKTRACE isn't set at compile time.

So there will always be a compile-time cost (unlike with a preprocessor macro, something will be produced then thrown away and the analysis of what to throw away will take longer with more code present), but not a run-time cost if the logging is not enabled, assuming you don't have any logic in the trace lines beside the calls to this module.

The problem is calling anything zero cost is inherently wrong. Nothing in life has no cost. I know I am being pedantic but I think a more accurate description is "zero production runtime cost," which is also how I interpret rust's zero cost abstraction. In that case too, I find the terminology grating because there are very obviously costs to using rust and its abstractions.

One obvious cost to code that is removed in production is that there is now a divergence between what your source code says it does and what it actually does. I now need to keep in mind the context the program is running in. There is a cost to that. It might be worth it, but it isn't zero.

Yeah I want a log package where the log statements that don't log also don't have their arguments evaluated. Half of my gc pressure is little strings created to call the log package that don't get logged.

So what you want is slog, found in the standard library? The doc.go file found in the package even goes into detail about what you (seem to) describe and how it avoids that problem.

A link to doc.go, for the lazy:

https://cs.opensource.google/go/x/exp/+/645b1fa8:slog/doc.go...

Basically it says to pass in objects, not prepared strings, so the formatted output is only computed if the message is actually logged. That object can be a struct implementing the LogValuer interface to do any required work/produce formatted output.

Oh cool, I hadn't run into LogValuer, that's cool. And passing pointers to strings rather than strings, hmmm.

> And passing pointers to strings rather than strings, hmmm.

Are you referring to the "URL" example? That isn't a case of passing pointers to strings, that is passing a URL (fmt.Stringer) where the String method has a pointer receiver.

To demonstrate why the pointer would be needed in that case, consider:

    type URL struct{}
    func (*URL) String() string { return "https://..." }
    func print(v any)           { fmt.Println(v.(fmt.Stringer).String()) }
    func main() {
        print(&URL{}) // ok
        print(URL{})  // panic
    }