指向接口的指针有意义吗?

huangapple go评论64阅读模式
英文:

Does pointer to interface make sense?

问题

这段代码无法编译通过,因为type *IF没有MyMethod方法:

package main

type IF interface {
    MyMethod(i int)
}

type AType struct {
    I *IF
}

func (a *AType) aFunc() {
    a.I.MyMethod(1)
}

然而,如果将I定义为IF本身,代码可以编译通过:

package main

type IF interface {
    MyMethod(i int)
}

type AType struct {
    I IF          // 不是指针
}

func (a *AType) aFunc() {
    a.I.MyMethod(1)
}

在我的情况下,I应该是一个指向实现了IF接口的记录的指针,但为什么不允许这样定义呢?

英文:

This code does not compile - type *IF does not have method MyMethod:

  1 package main
  2 
  3 type IF interface {
  4     MyMethod(i int)
  5 }
  6 
  7 type AType struct {
  8     I *IF
  9 }
 10 
 11 func (a *AType) aFunc() {
 12     a.I.MyMethod(1)
 13 }

However, it compiles OK if I is defined as an IF itself:

  1 package main
  2 
  3 type IF interface {
  4     MyMethod(i int)
  5 }
  6 
  7 type AType struct {
  8     I IF          // not a pointer
  9 }
 10 
 11 func (a *AType) aFunc() {
 12     a.I.MyMethod(1)
 13 }

In my case, the I should be pointer to a record that implements IF, but why is it not allowed?

答案1

得分: 2

在我的情况下,I 应该是指向实现了 IF 接口的记录的指针,但为什么不允许呢?

如果这是你的目标,那么你的方法是错误的。接口是一个包装器,是一个保证行为的契约。如果你的接口要求方法改变实现类型,那么实现类型应该是一个指针。指向接口本身的指针并不会使实现类型变为可变。

可以这样理解,如果我有一个这样的结构体:

type A struct {
    b MyType
}

type A2 struct {
    b *MyType
}

mt := MyType{/* 初始化操作 */}
a := &A{mt}
a2 := A2{&mt}

那么 aa2 是不等价的。如果我想要一个指向 mt 的指针,那么应该使用 A2,而不是 A。即使我尝试像 &a.b 这样的操作,它也永远不会是指向原始 mt 的指针。将接口视为相同的方式,其中 b 是实现接口的类型。你试图在 I 上做的事情相当于拿 &A{} 并试图将其用作指向 mt 的指针,而你应该使用 A2

当你有

func (mt MyType) MyMethod(i int) {

}

那么该接口将永远不会持有除 mt 的副本之外的任何内容。如果你想要一个“指向记录的指针”,你必须实现

func (mt *MyType) MyMethod(i int) {

}

这与上面 A 和 A2 的区别是等价的,如果你使用第一种实现,你将永远无法获得指向原始对象的指针。

至于为什么在 Go 中,尽管通常具有透明指针,你不能在指向接口的指针上直接调用方法而必须显式解引用,我不清楚其原理。然而,如果你确实仍然想要一个指向接口包装器的指针,可以在指针实现中调用 MyMethod(1),它将等效于 (*a.I).MyMethod(1)。只需注意,如果 IF 的实现者是一个裸类型而不是指向类型的指针,这不会改变代码的行为。

现在来回答最后一个问题:为什么没有类似于“指向实现该接口的裸类型的指针”的结构?根据我的直觉,原因是接口关注的是行为,而不是数据。你会对这样的指针做什么呢?尽管有指针,方法仍然会在裸类型上调用,并且你无法访问任何字段,因为它们不被接口公开。你也不能写入指向的位置,因为类型可能不匹配。这将是一个几乎无用的结构。

你可以通过使用反射来获得一个等效的断言,即“这是一个指向实现我的接口的数据的指针吗?”但在大多数情况下,这可能不是一个非常有趣或有用的信息。

英文:

> In my case, the I should be pointer to a record that implements IF,
> but why is it not allowed?

If this is your goal, you're going about it incorrectly. An interface is a wrapper, and a contract that guarantees behavior. If your interface requires that the methods mutate the implementing type, then the implementing type should be a pointer. A pointer to the interface itself will not suddenly make the implementing type mutable.

Think of it this way, if I have a struct like this:

type A struct {
    b MyType
}

type A2 struct {
    b *MyType
}

mt := MyType{/* initialization stuff */}
a := &A{mt}
a2 := A2{&mt}

Then a and a2 are not equivalent. If I want a pointer to mt, then I should use A2, not A. Even if I try my hardest and do stuff like &a.b, it will never be a pointer to the original mt. Think of an interface the same way, but where b is the type implementing the interface. What you're trying to do with I is equivalent to taking &A{} and attempting to use it as a pointer to mt, when you should be using A2.

When you have

func (mt MyType) MyMethod(i int) {

}

Then that interface will never hold anything other than a copy of mt. If you want a "pointer to the record" you must implement

func (mt *MyType) MyMethod(i int) {

}

This is equivalent to the difference between A and A2 above, you'll simply never be able to get a pointer to the original object if you use the first implementation.

As for why, exactly, you can't call methods on a pointer to an interface without explicitly dereferencing it despite Go typically having transparent pointers, I'm not clear on the rationale. However, if you really still want a pointer to the interface-wrapper, calling MyMethod(1) should work as (*a.I).MyMethod(1) in the pointer implementation. Just be aware that this won't alter the behavior of the code at all if the implementer of IF is a naked type and not a pointer to a type.

Now for the final question: why is there no construct similar to "a pointer to a naked type that implements this interface"? The reason, to the best of my intuition, is that an interface is about behavior, not data. What would you do with such a pointer? The methods will still be called on the naked type despite having a pointer, and you don't have access to any of its fields since they're not exposed by the interface. You also couldn't write to the location pointed to because the the types may not match. It would be a mostly useless construct.

You could probably get an equivalent assertion to "is this a pointer to data that implements my interface?" by using reflection, but in most cases it likely wouldn't be a very interesting or useful thing to know.

huangapple
  • 本文由 发表于 2014年1月1日 12:30:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/20865537.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定