Go interface

我们知道 Golang 中有两种方法:值方法和指针方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type person struct{}

func (p *person) Speaker() {
	fmt.Println("Hello.")
}

type fox struct{}

func (f fox) Speaker() {
	fmt.Println("firefox.")
}

官方对函数的调用规则说明为: A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x’s method set contains m, x.m() is shorthand for (&x).m().

简单来说,指针变量既可以调用指针方法,也可以调用值方法。而值变量只能调用值方法。

但为了方便,官方做了个语法糖,即当你拥有某个变量时可以调用它的任何方法。无论是值方法还是指针方法。因此以下代码有效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
type person struct{}

func (p *person) Speaker() {
	fmt.Println("Hello.")
}

func (p person) Speaker_value() {
	fmt.Println("VauleFunc: Hello.")
}

func main() {
	p := &person{}
	p.Speaker()
	p.Speaker_value()

	p_value := person{}
	p_value.Speaker()
	p_value.Speaker_value()
}

以上在编译时,编译器会自动对值类型的指针调用进行转换:

1
2
// p_value.Speaker()
(&p_value).Speaker()

总结:在以下两种情况下调用指针接收器方法都是合法的:操作对象本身是指针,或可获取其地址(如上例所示)。而调用值接收器方法时,操作对象是值或可解引用的指针(如任何指针类型,规范中对此有明确说明)均属合法。

interface

interface 实际上是一种结构,其有两个元数据:tab 指向存储接口类型以及传递给接口具体信息的数据,data 指向传递给接口的值。注意:interface 存储的值被设计为不可寻址

值得一提的是,映射(map)的元素也是不可寻址,而 slice 的元素可以寻址。但是,slice 取址的对象可能会在 slice 底层变动后发生复制,使得二者指向不同的对象,因此寻址一个 slice 元素不是好点子。

1
2
3
4
type iface struct {
	tab  *itab
	data unsafe.Pointer
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Speak interface {
	Speaker()
}

	var speak_person Speak
	speak_person = &person{}
	speak_person.Speaker()

	// speak_person=person{}    错误:person的Speaker是指针调用,如果speak_person存储值类型,将无法进行指针调用

	var speak_fox Speak
	speak_fox = &fox{}  // 即便 speak_fox 存储指针类型,也可以进行值调用
	speak_fox.Speaker()

	speak_fox = fox{}
	speak_fox.Speaker()

因此,一个 interface 调用方法的具体流程可被分为以下类型,但它们的主要流程都是一样的:

  1. data解引用
  2. 断言其具体类型
  3. 调用方法。

interface 存储指针且方法为指针调用

interface 首先解引用 data,然后断言其为具体类型,接着进行调用。

1
2
3
4
5
6
dataCopy := *iface.data // dereference iface.data
typedData := dataCopy.(*Person) // assert

Person_SPEAK(
	typedData
)

interface 存储指针且方法为值调用

interface 首先解引用 data,再次对指针解引用,然后断言其为具体类型,接着进行调用。

1
2
3
4
5
6
7
dataCopy := *iface.data // dereference iface.data
dereferenced := *dataCopy // dereference the pointer at iface.data
typedData := dereferenced.(Person) // assert

Person_SPEAK(
	typedData
)

interface 存储值且方法为值调用

interface 首先解引用 data,然后断言其为具体类型,接着进行调用。

1
2
3
4
5
6
dataCopy := *iface.data // dereference (extract the value)
typedData := dataCopy.(Person) // assert

Person_SPEAK(
	typedData
)

进一步讨论

以下内容来自Go interfaces, the tricky parts,写的非常好。

为什么以下代码无法编译:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

func main() {
    users := []User{User{"alice"}, User{"bob"}}

    // hint: this line compiles, User fulfils Named
    var _ Named = User{"charlie"}

    getName(users[0])
    getNames(users)
}

type Named interface{ name() string }
type User struct{ fullName string }

func (u User) name() string { return u.fullName }

func getName(n Named) string       { return n.name() }
func getNames(ns []Named) []string { return nil }

在编译 getNames(users) 时,编译器会提示:

cannot use users (variable of type []User) as []Named value in argument to getNames

为什么?User 实现了 name 方法,并且var _ Named = User{"charlie"}也表明 golang 确认了这一点。

实际上问题更加严重:

  • 我无法将 []User 作为 []Named 传递
  • 我无法将 []User 作为 []interface{} 传递
  • 我无法将 *User 作为 *Named 传递

在 golang 中,interface 和具体结构是两种不同的东西,不能因为 interface 可以包含具体结构而将二者混为一谈。同时,golang 不会自动进行转换处理,对于编译器来说,getNames(users) 就是在试图传递不同元素类型的 slice。

同样,*User 和 *Named 是两个指向不同类型的指针,golang 不会进行自动转换。

参考

updatedupdated2025-11-042025-11-04