Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visibility of embedded private interfaces in Go

If I embed an interface with a lowercase name (private) into another interface with an uppercase name (public), I assume that code outside of the defining package cannot see the embedded private interface. Is this assumption correct?

type b interface {
    G() int
}

type A interface {
    F() string
    b
}

Code outside of the defining package cannot "see" the embedded b, correct? Outside code cannot call G through an instance of A.

like image 893
Ralph Avatar asked Feb 21 '18 13:02

Ralph


1 Answers

Embedding unexported interface in interface

Interface embedding in interface is nothing more than merging the method set of the embedded interface into the embedder, so it becomes part of the method set of the embedder type. It doesn't matter if the embedded interface type is exported or not. Quoting from Spec: Interface types:

An interface T may use a (possibly qualified) interface type name E in place of a method specification. This is called embedding interface E in T; it adds all (exported and non-exported) methods of E to the interface T.

All that happens in your code is that A will be an interface with 2 methods: F() string and G() int. There won't be a "field" like A.b because A is an interface type, not a struct. So this isn't particularly "interesting".

How can you try it?

Create a folder subplay in any package, subplay.go:

package subplay

type b interface {
    G() int
}

type A interface {
    F() string
    b
}

Create another go file that imports this subplay, e.g. play.go:

package main

import "play/subplay"

func main() {
    var a subplay.A
    a.G()
}

And it compiles. Runtime panic occurs though because a is not initialized (or rather left being nil), but if it would be, the a.G() call would not panic.

With the following additions there will be no run-time panic:

In subplay.go, add:

type aimpl struct{}

func (aimpl) F() string { return "aimpl.F() called" }
func (aimpl) G() int    { return 1 }

func NewA() A {
    return aimpl{}
}

And in play.go:

func main() {
    var a subplay.A
    a = subplay.NewA()
    a.G()
}

The above code compiles and runs, and does not panic.

Also note that you can create a type in another package that implements subplay.A, you do not need to refer to subplay.b, because all that matters is the method set. The following another type also implements subplay.A, you can place this in play.go:

type another struct{}

func (another) F() string { return "aimpl.F() called" }
func (another) G() int    { return 1 }

func main() {
    var a subplay.A
    a = another{}
    a.G()
}

This again compiles and runs without run-time panic.

Embedding unexported interface in struct

A similar and more interesting case would be to embed an unexported struct (or an interface) type in a struct (and not in an interface), which "truly" creates an A.b field. Moreover, fields and methods of the embedded type get promoted to the embedder as if they would be fields or methods of the embedder. Quoting from Spec: Struct types:

A field or method f of an embedded field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.

Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in composite literals of the struct.

Code outside of the defining package cannot refer to the embedded field A.b, this is correct, but code outside of the defining package can call the promoted A.G() method because this identifier is not lower-cased, so the restriction does not apply to it.

The restriction for non-exported identifiers is enforced by the compiler when you attempt to refer to them. Writing a.b.G() from another package is a compile-time error, because you are referring to a non-exported identifier (a.b). When you write a.G(), you are not referring to any non-exported identifiers, only to the exported a.G identifier, so it is allowed.

like image 105
icza Avatar answered Sep 24 '22 01:09

icza