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))