简体   繁体   中英

What does “dynamic type” mean in a Go interface?

The Way to Go: A Thorough Introduction To The Go Programming Language (Ivo Balbaert) contains this sentence which I don't quite understand:

An interface type can contain a reference to an instance of any of the types that implement the interface (an interface has what is called a dynamic type)

What is an example of this, and why this is useful?

Say you have an interface:

type I interface{ F() }

And two implementations of said interface:

type S struct{}
func (S) F() { }

type T struct{}
func (T) F() { }

Then:

var x I
x = S{}

Now the static type of x is I , and its dynamic type is S .

You can reassign x a value of a different type that implements I :

x = T{}

Now the static type of x is still I (it will never change), and its dynamic type is T .

IOW: the dynamic type of an interface value is the type of the value that was originally converted to an interface type.

Definition

An interface has what is called a dynamic type

A dynamic type means that it can hold a reference to different types (eg string, int, ...) and that it can change at runtime, whereas a static type is checked at compile time and cannot change.

However, the definition given by the book is questioned. According to the official Golang website :

Some people say that Go's interfaces are dynamically typed, but that is misleading. They are statically typed: a variable of interface type always has the same static type, and even though at run time the value stored in the interface variable may change type, that value will always satisfy the interface.

Source

Example

Even though an interface is not truly a dynamic type, here is how to use them.

Say you have the following interface.

type Locker interface {
  Lock()
  Unlock()
}

That's actually the Locker from sync package.

Now if you create two structs that implement the functions defined by the Locker interface. In other words, if you fulfil the Locker contract you will be able to use the structs Foo and Bar as a Locker interface.

type Foo struct {
  A string
}

func (f *Foo) String() string {
  return f.A
}

func (f *Foo) Lock() {
  // ...
}

func (f *Foo) Unlock() {
  // ...
}

type Bar struct {}

func (b *Bar) Lock() {
  // ...
}

func (b *Bar) Unlock() {
  // ...
}

So given the definition you gave :

An interface type can contain a reference to an instance of any of the types that implement the interface (an interface has what is called a dynamic type)

It could be translated into :

A Locker (interface) type can contain a reference to an instance of any of the types that implement its contract (eg Foo, Bar, ...).

Which in code means :

var lock Locker

lock = &Foo{"Foo"} // We assign an instance of type Foo to a Locker var
lock.Lock() // We can call all functions defined by the interface Locker
lock.Unlock()
lock.String() // This won't work because the Locker interface does not define the String() function, even though Foo implements it. 

lock = &Bar{}
lock.Lock()

In the example above we can see that the variable lock holds a reference to different types, but it is not truly dynamic because the condition to assign a type to lock is that its type comply with the Locker contract. And that part is defined at compile time.

Why is it useful?

This post will explain why interfaces are useful better than me. https://softwareengineering.stackexchange.com/questions/108240/why-are-interfaces-useful

Every variable has a type. That type is either an a static type (int, string, bool, map, struct, slice, etc) or an interface type.

An interface can be implemented by any static type (typically by an aliased type).

A variable of an interface type is actually stored in two parts. The first part is the symbolic name of the underlying static type. The second part is the data in the format of that static type.

So if a variable is declared to be of an interface type, that means that its type is dynamic in the sense that the underlying type could end up being any of the static types that implement the interface.

Typically the usefulness of this pattern is to define an extended behavior of a class of types from some shared behaviour. Doing this you gain the ability to encapsulate the functionality that you care about in any particular case, rather than worrying about the specifics of a given type. For example, any Reader can be Read from, and once you've decided that you have a reader you don't need to worry about all the other methods that underlying type might have, meaning that you can easily define a single function that uses any reader.

This is close to polymorphism and shares most of the benefits.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM