Anatomy of Interfaces in Go

In the process of getting to know Go, I found an example in the documentation:

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p // Will always return a non-nil error.
}

After watching it, I wondered why returnsError will always return a non-nil error?

Variables in Go are always value-initialized. This also applies to interfaces. It should be noted that interfaces are implemented as two elements: type(T) and value(V). This is a rather superficial definition, which we will analyze a little further. The interface value will be nilonly if Vand T both will nil.

There is an interesting point, namely the case when V=nila T!=nil. In this case, no interface checks on nil they won’t help us. And this is exactly the scenario that happens in returnsError.

I was wondering exactly how these checks work in Go.

type Word struct {
	name     string
	priority uint
}

type Foo interface {
	foo()
}

func (w *Word) foo() {
	fmt.Println("call foo()")
}

func (w *Word) noFoo() {
	fmt.Println("call noFoo()")
}

func call(f Foo) {
	if f != nil {
		f.foo()
	} else {
		fmt.Println("f null")
	}
}

func main() {
	var f1 *Word
	call(f1)
}

There are checks in the above code. Everything works without errors. The output will be:

call foo()

It seems a little strange, because they transferred nil and in general it was expected that either would not pass the test for nilor fire on execution.
Let’s figure it out.

Function call accepts an interface Foo. How does pointer conversion happen? Word to interface Foo?
Simplified interface looks like this:

Interface:
1. static type
2. dynamic information:
– dynamic type
– dynamic value

In our example, the static type would be Foodynamic type Wordmeaning nil.

Let’s see in more detail.

Languages ​​with methods can be divided into two types: create tables of virtual methods (for example, C++) or perform a method lookup on each call (Smalltalk). In the second case, caching is used for optimizations. Go is in the middle: it has a method table, but it is calculated at runtime.

What are the interfaces in Go?

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

data – this is directly our f1 from the example. All information on methods, types and other things is hidden in tab – interface table.

type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

note that itab corresponds to a static interface type (Foo), rather than dynamic (Word). For greater accuracy, it corresponds to the pair FooWord. So inside itab there will only be methods that satisfy the interface Foo and noFoo won’t be here.

inter – contains metadata about the static interface type.
_type – about dynamic type
fun – a set of pointers to interface methods. is variable sized.

Where does itable?

itab – represents a kind of key value, namely, the correspondence of a static type to a dynamic one (remember I spoke above aboutFooWord ?). There are a lot of types and interfaces in large programs. And not all combinations are needed. To do this, the Go compiler creates several type description tables. The first contains a list of methods for a particular interface. In the second, which methods contain dynamic types. Our itable is the correspondence between the two tables. itable is cached after creation, so this match only needs to be evaluated once.

How is the method f.foo() will be called and why is there no error?

There will be some analogue:

f.tab.fun[0](f.data)

We figured out what will happen in fun[0] (the only method of our interface Foo) and what it contains data (the object itself Wordin the example we passed a pointer to it uninitialized – nil).
In some languages ​​(for example, C ++) for methods there is such a thing as this-call. It means that the first hidden parameter of any method is a pointer to the object itself (this). And if you need to change / read the fields of the object, we access them implicitly through this. At the same time, there are static methods that can be called without creating an object – for such methods this not passed implicitly not needed.
In our example, this is exactly what happens. Method foo does not interact with Word. And a poet f.data (which nil) is not used and the program exits gracefully.

If we change the implementation of the method, then everything will fall as it should:

func (w *Word) foo() {
	fmt.Printf("call foo(): %s\n", w.name)
}

Previously there was a statement:
The interface value will be nilonly if Vand T both will nil. If we talk about the code now: the value of the interface will be nil only if data and _type equal nil at the same time, because the option when only data equals nil, is quite correct. A null interface value that does not contain a value per se is not the same as an interface value that contains a null pointer, as we saw by looking at structures.

Thank you for your attention.

Similar Posts

Leave a Reply Cancel reply