Why does Go allow a struct to implement an unexported interface present in a different package?

Issue

I wrote a sample code to understand how the unexported interface works. In the below example, I have declared the unexported repoInterface in the service package.

TestRepo struct in the repo package implements the unexported repoInterface without any issues.

Code structure
repo
  repo.go
service
  service.go
main.go

service.go

// service/service.go

// this is the interface which the TestRepo struct implements in repo package
type repoInterface interface{
    GetName() string
}

type TestService struct{
    repo repoInterface
}

func NewTestService(r repoInterface) TestService {
    return TestService{
       repo: r,
    }
}

func (s TestService) GetName() string {
    return s.repo.GetName()
}

repo/repo.go

// repo/repo.go
type TestRepo struct{
    name string
}

func NewTestRepo(name string) TestRepo {
    return TestRepo{
        name: name,
    }
}

// implements repoInterface present in service package
func (r TestRepo) GetName() string {
    return r.name
}

main.go

func main() {
    testRepo := repo.NewTestRepo("hello")
    testService := service.NewTestService(testRepo)
    fmt.Println(testService.GetName())
}

// Output
// hello

My assumption so far:

This isn’t possible since repo and service
are different packages.

TestRepo struct present in repo package cannot implement the Unexported interface present in the service package.
This is the reason why we export interfaces.

Now I realized that this is not true and my understanding is wrong.

Question:

Why does Go allow to implement an unexported interface present in a different package?

Solution

service.NewTestService package function requires any value that implements the type interface{ GetName() string }.

repo package exports a type TestRepo which exposes a method GetName() string.

Upon passing the repo.TestRepo to the service.NewTestService function like in service.NewTestService(testRepo), the value implements the interface by providing the expected method set.

All good.

That the type service.repoInterface declares a not exported identifier only discriminates the packages that can use that interface name.

I have reproduced your example on the play https://go.dev/play/p/bp6z2HjwdLS


An interface type declaration containing a not exported identifier is a sealed interface.

Those sealed interfaces can not be implemented by a foreign package.

It can be a not exported method name like in

type Fooer interface {
    Foo() 
    sealed()
}

Try here https://go.dev/play/p/3Syh7R0uS-q

It can also declare a method using a not exported argument type,


type Foo interface {
    GetName() string
    GetName2() sealed
}

type sealed int

Answered By – mh-cbon

Answer Checked By – David Marino (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.