Variables declaration - func and package levels

I’m trying to understand the philosophy behind why Go-Compiler complains about variables declared but not used inside a func but not when declared at the packet level?

Are declared variables not used at the packet level silently removed or maintained for other scenarios?

1 Like

The compiler knows about unused function variables. The compiler does not know about unused package variables, the linker does.

2 Likes

That does not answer the question, why unused function-scoped variables raise an error while unused globals do not.

2 Likes

The question was “why does the compiler not complain”, this question has been answered, with “it can’t know”.

A possible follow up question would be, why does the linker not complain. Though that will probably something simple as “no one ever though about implementing this”.

On a quick glance I found no complaints about this issue in the bug tracker.

2 Likes

As a newbie, I just learned about “linker” but the question can be amended to “Why the compilation-process doesn’t complain about unused declared variables at package level”

Calm down guys. There are 2 parts of questions here:

  1. Why function complaint about unused variables?
  2. What happened to package level’s unused (private) variables?

For the 1st question: it is because function is part of stack memory storing execution data. If you declare but unused variable, a portion of memory will be allocated and wasted. Another good reason for removing them is to reduce potential memory related bugs as a whole.

@petrus answered the in-depth technical aspect correctly: the compiler does compile functions which hence has the visibility of the unused function.

That is why the compiler will and always complain about unused variables inside a function.

Resource:

https://golang.org/doc/effective_go#blank_unused


As for your second part of the answer, I could not find a proper spec mentioning about their unused consequences either. In fact, because of your interesting question, I actually tweeted to one of the developer asking help to locate that documentation for answering this question.

Like @NobbZ said, which sparked my curiosity as well: why didn’t the linker complains about it?! My hunch tells me you had found a bug but there are no supporting evidences. Hence, the tweet is needed.

There is a quick and hackish way: compile a hello world with unused package-level variable and dissemble it and then then analyze the assembler codes to locate the variable. However, I’m a bit tight in schedule so if anyone is interested, please go ahead and try it out (use amd64 CPU as their datasheet is much easier to read):


As for why it slipped through my sensors: because I don’t leave unused codes (not just variable) in any of my authorship (why would I?).

Secondly, I rarely use package level variables (both exported and private). Haven’t found any practical use cases for it. However, I used A LOT of constants.

1 Like

Thank you for the details @hollowaykeanho
For the first part, the same philosophy should apply, if it’s a waste within func, then it can be removed as a dead code, similar to the func level one.

Anyway, I wasn’t familiar with the details of compilation process but I just read the following documents, and they do refer to removing the “dead code” but none explains why not complaining about it, or how to optionally enable it.

Depending on your perspectives, strictly speaking, compile and linking are 2 different stages of work. The overall nutshell is compile translates your human friendly codes into CPU-friendly binary instructions while linking is to “glue” multiple binary instructions together.

However, from general Go’s user perspectives, both compile and linking are viewed as one: build since you’re not instructed to manually link multiple Go binaries anywhere else. Hence, I expect it to complain.

Philosophically wise, I strongly and firmly believe that the same philosophy applies to package level at well, specifically looking at private variables (exported variables are well, exported for external usage of course). That’s why in my opinion, you stumbled into a bug when asking an unexplored territory.

On second thought, you can email to golang-nuts mailing list asking specifically about the 2nd question. It’s qualified already:

https://groups.google.com/g/golang-nuts

1 Like

Unused package-level variables don’t go into the binary - they are thrown away by the linker…

2 Likes

Fun fact for @hollowaykeanho: another very quick (and very dirty) way to check if a variable is included into the binary is to compile var a = "Lorem Ipsum 12" and use grep to search for that string directly on the binary.

2 Likes

Great. A supporting Exhibits data for my hypothesis. Thanks for the effort. :joy:

Haha. Yeah. Good trick to skip the datasheet comparison. I was too used to datasheet comparison. :cold_sweat:

1 Like

I just emailed them. It would be a radical encounter if it’s a bug, which I think it is at least at philosophical level.

Just in case someone else is interested in the topic, Ian Taylor provided the following response

Package scope variables can provide hooks for other packages (if the variables are exported) or for debuggers to change program behavior.
There’s no easy way for the compiler or linker to know whether this might happen.

In particular the compiler (perhaps unfortunately) supports a go:linkname directive that can reach into another package and refer to a package-scope variable. This is generally not good practice but there is existing code that does it.

1 Like

@Jad, given the explanation from Ian:

there is existing code that does it.

It is clear that the feature is only meant for debugger stays for backward compatibility.

It is not a good practice

Means to avoid it.


TL; DR: Don’t worry. It’s always the correct practice to remove unused codes (not just variable) :woozy_face:.

No other reason even for recreational purpose. I don’t think Go ecosystem allows one to use Go to participate in The International Obfuscated Code Contest like this one: https://www.ioccc.org/years.html.


FYI:

I disagree with meera’s explanation because those control variables she(?) mentioned are indeed being used as default values’ feeder at some point of executions, regardless on how you wrap with build flags, debug control variables, and etc. Please ignore that.

The definition of unused variable we’re talking about is this:

package main

import (
	"fmt"
)

var (
	unusedA = "I'm definitely not used"
)

func main() {
	fmt.Println("Hello, playground")
}

and not:

package main

import (
	"fmt"
)

var (
	name = "Playground" // Switch between testing state / production state
)

func main() {
	fmt.Printf("Hello, %s\n", name)  // this is being used
}

Exported variables like the following must stay because you’re intentionally facilitating your package users’ to alter it.

package main

import (
	"fmt"
)

var (
	// Set a name for package to print out loud
	Name = "Playground"
)

func SayHello() {
	fmt.Printf("Hello, %s\n", Name)
}
1 Like

Thank you for the elaboration @hollowaykeanho and to be honest, I’m not fully convinced with Ian’s answer, perhaps can’t fully understand it yet as a newcomer to the language, and surely cannot go in a debate about it with someone with Ian’s capacity yet.

My humble understanding at philosophical level concludes that if the build process can clean up even imported but not used packages, then it better clean up un-exported declared variables at package level.

And if there is no code for it now, maybe it should be added to the roadmap to maintain Go’s promise of enforcing clean and usable code.

Maybe I went off-road while “studying” the language, but I’m in that foggy area now and cannot tell where I stand in my understanding, and it will surely consume some time before I can come back to this point and rethink it. Maybe at that point of time I wouldn’t give it any weight at least to maintain if it’s working don’t fix it :wink:

1 Like