Why can non-internal packages expose internals?

In a Go module containing an internal and a non-internal package, the non-internal one can expose, e.g., structs of the internal one. Upon encountering this behavior, I assumed it was a bug, but was assured at cmd/go: Contents of internal package can be exposed through non-internal package · Issue #71894 · golang/go · GitHub that it is intentional.

Here is an example, where the non-internal function Foo can expose the internal struct Bar:

$ cat foo/internal/bar/bar.go
package bar

type Bar struct {
        X int
}

$ cat foo/foo.go
package foo

import "foo/internal/bar"

func Foo() bar.Bar {
        return bar.Bar{X: 42}
}

The compiler does not complain about this when using the return value of Foo in another Go module. Why is it so? Is this one of these situations where the Go authors give us “a sharp knife” for special occasions, assuming that we developers know not to misuse it?

Go has a model that everything written with the capital letter is exported. No matter if you call your package internal. If you don’t want someone to get access to the internals leave only api and make everything none exported

I don’t understand. An internal package without any exports is completely useless, as there is no way to interact with it.

Are you aware, that internal has special meaning in to package paths? See go command - cmd/go - Go Packages

Yes I am aware. And I guess you need to read this article again. It clearly states that internal can be imported in any package rooted to the internal. In your example you do exactly this. And this means if your internal things are exported, then you can get access to them. Even in GitHub issue there are lots of links to discussions about misunderstanding around internal name. cmd/go: modules: exposure of internal packages · Issue #30569 · golang/go · GitHub

I’m sorry, I think we’re getting off on the wrong foot here. I’m well aware that the foo package should be able to use the foo/internal/bar package. This is working as intended.

What surprised me was, that I was able to use the the Bar struct of the internal package of the foo module in another Go module. For example, in a different Go module, I was able to access the Field of the internal struct:

$ cat baz/baz.go
package main

import "fmt"
import "foo"

func main() {
        fmt.Println(foo.Foo().X)
}

$ cd baz && go run .
42

Your question is very strange, it’s similar to this:

type b struct {
}

func FuncA() *b {
	return &b{}
}

Don’t limit yourself to features. Closures are just to isolate something from being called externally, but that doesn’t mean that the things thrown can’t be used.

Huh, I hadn’t thought about it this way, thanks for the example.