Hacker News new | ask | show | jobs
by knorker 1916 days ago
I'm not sure I follow. How can the compiler know that the import is side-effect free, transitively?

Without opening more files and parsing them, how does it know that none of the transitive dependency tree has a `func init(){}` ?

Go forces you to add an underscore if you mean "I'm importing this for its side-effects, and am not using it directly".

1 comments

If it doesn’t know whether it is used or not, how can it fail a compile?
It doesn't know what the author's intention is:

"import for the side-effects", or "oops, that was a mistake".

It's not that it fails, it's that it rejects.

This just adds to my impression that Go is a very poorly designed language.

So. The compiler knows that the package is not used. How can you tell the compiler that the package is imported for side-effects?

Oh, you have to explicitly tell it to the compiler by, you know, actually using the import, right. By doing something stupid like

  import (
    _ "github.com/lib/pq"
    _ "image/png"
    ...
  )
Oh. Look. Problem solved. You mark a package as "I imported this specifically for its side effects". So the compiler can go ahead and include it.

Why does it need to include any other unused module?

Programming is the practice of telling the computer your intentions as code.

Why are you upset that Go forces you to be explicit about your intentions, when it's ambiguous?

E.g. take C++ single-argument constructors. It's the reason we have guidelines like this: https://rules.sonarsource.com/cpp/RSPEC-1709

Go is opinionated on formatting and being explicit about unused imports. It's forcing you to do the right thing.

C++ and Python let cruft accumulate, in headers and imports, with extra tooling external to the compiler (iwyu) to detect when you made a mistake.

There are valid criticism of Go, and I have many, but this ain't one.

> Why does it need to include any other unused module?

Because without at the very least parse the entire dependency tree of the module (which could be huge) it cannot know if it's unused.

Hell, it probably NEVER is unused, because many modules will have "var ErrFoo = errors.New(...)". If it calls C code in these global variables or "func init()" then there isn't even in theory a way that the compiler can deduce that it's unused.

This is a solution to a real problem, where other languages take forever to build, and create bloated binaries.

> Why does it need to include any other unused module?

If you import it then it's not unused, and it's extremely common that that's a mistake. That's why.

Have you ever coded C++, and done a refactor? How many times do you just leave "#include <sys/types.h>" because you don't know exactly why you imported it?

Have you coded Python? Do you know that the previous author is not importing a given package for its side-effects?

> It's forcing you to do the right thing.

Mmm... No. It's just post-hoc justification of the design.

> > Why does it need to include any other unused module?

> Because without at the very least parse the entire dependency tree of the module (which could be huge) it cannot know if it's unused.

Once again: no. Because it already knows it's unused and stops the compilation. So it already knows it's unused.

> If you import it then it's not unused, and it's extremely common that that's a mistake. That's why.

Once again:

- the compiler has already parsed everything

- the compiler already knows that the import is unused

- the compiler stops the compilation at this point ... because otherwise it would have to include anyway even though at this point it already knows that the import is unused

> How many times do you just leave "#include <sys/types.h>" because you don't know exactly why you imported it?

There are other ways of dealing with this than halting the compilation entirely.

> No. It's just post-hoc justification of the design.

Do you have any reason to think that? E.g. documented or discussions at the time?

> Once again: no. Because it already knows it's unused and stops the compilation.

Hmm… ok, I see what you may be saying now. Am I right in saying that you would prefer that Go would silently ignore any non-underscore imports that aren't referenced by name?

So if I import "fmt" and don't use it, then the compiler won't even try to open the associated pkg files?

And if I want the compiler to override that (because I'm importing for the side effects), then I'll have to do the underscore trick to force the import even though it's unused?

Is that what you mean?

That seems like it would be a gotcha. As a reader of the code (not the compiler) I would then not be able to see the list of imports and actually know which ones actually get imported. Like, dammit why doesn't sql support pgsql? Imported the pgsql driver, it's right there!

If you mean "no, not silently ignore. Give a warning", then that runs into the (eyerolling) "the Go compiler doesn't have warnings. It runs effectively -Werror. It's eyerolling because it's clearly not true, as they've simply outsourced those second class warnings to govet and other tools.

But anyway, Go's forcing of this makes it so that the reader actually knows what actually gets imported.

I hope you at least recognize that because of side effects (which is true in Python (code executes on import), C++ (global variable constructors), C (attribute constructor), and others, importing and not using does not create the same behavior as not importing at all?

I don't want the compiler to make that choice for me.

If the Go compiler second-guessed an import, it would be an outlier among languages. The three others I listed don't.

And Go is baking IWYU into the compiler. You may not like it, but it's not stupid.

> There are other ways of dealing with this than halting the compilation entirely.

Oh I agree. I find the "unused variable" compile error to be hugely frustrating during development. Having to work around with "a = a" or wrapping code in "if false {}" instead of commenting out is ridiculous.

But the import one I agree with.

I’m not familiar with go, but why on Earth can an import have side-effects? That’s ridiculous..

I was never a fan of Go, but this just takes the cake..

Many languages do.

A Python import will execute anything it imports (most of what's "executed" is "def" and "class"), but if there's code at the top level, it'll run.

C++ will run constructors for global variables before main().

Even C has side-effects from linking in!

    void __attribute__ ((constructor)) init() {}
So I don't mean side-effects at compile time (e.g. it's not like C macros), but importing / linking something even if you don't use it is very common that it has side effects.

Why would you do this? Commonly to "register a handler", to not have to both "import foo" and call "foo.init()"

To be honest, I think of header files really lowly.

I do see now why would one use that, thank you! Though I still think that adding an explicit keyword to run init functions would be preferable, like `import init package`.

That's what the underscore is. :-)

Other init code is stuff like "var foo = regexp.MustCompile(...)".

Then there may be transitive imports that do C code, and it really needs to run its init code.