Home > Software engineering >  How are interface types handled between functions?
How are interface types handled between functions?

Time:09-17

I have some questions about interfaces, especially when these interfaces are passed between functions.

I understand that interfaces are satisfied implicitly, meaning the following code is valid:

type itemX struct {}
func (x *itemX) Do() string {
    return "itemX"
}

type Itf interface {
    Do() string
}

func test(i Itf) string {
    return i.Do()
}

func main() {
    x := new(itemX)
    str := test(x)  // valid, since x implicitly satisfies Itf
}

However, it is not so clear what happens or what the type contract is like when I start passing interfaces between functions. An example:

// itemX, Itf, and test have the same declaration as the above snippet

func returnsItf(i Itf) Itf {
    return i
}

func returnsTypeAssertedX(i Itf) Itf {
    return i.(*itemX)
}

func takeItf(i Itf) {}

func takeX(x *itemX) {}

func main() {
    x := new(itemX)
    var i Itf = x

    a := returnsItf(i)   // returns type Itf
    _ = takeItf(a)       // no error

    b := returnsTypeAssertedX(i)
    _ = takeItf(b) // no error, since *itemX implements Itf
    _ = takeX(b)   // error, cannot use b (type Itf as *itemX)
}

There seems to be some hidden behavior when an interface is passed out as a function return. If the return is *itemX and type is Itf, the return is transformed into Itf before the function frame is terminated.

So, this implicit check (concrete -> interface if type is interface) is done twice per function call:

  • at the start of each function call,
  • and at the end.

Is my understanding of this implicit transformation correct?

CodePudding user response:

An interface is a data type that has two members: The type of the underlying object, and a pointer to that object. So, wen you use a non-interface type in a context that needs an interface, the compiler constructs an interface type from that value, and uses that.

func returnsTypeAssertedX(i Itf) Itf {
    return i.(*itemX)
}

In the above function, it first type-asserts that the passed in argument is of the required type, and then converts the underlying value of the argument back to an interface.

b := returnsTypeAssertedX(i)
_ = takeX(b) 

The above will not work, because b is an interface{}, and takeX requires an *itemX. However, this would work:

takeX(b.(*itemX))
  •  Tags:  
  • go
  • Related