切片 vs 类型别名作为方法接收者

z11*_*11i 3 methods types go

我有一个Foo带有方法的结构print

type Foo struct {
    Bar string
}
func (f Foo) print() {
    fmt.Println(f.Bar)
}
Run Code Online (Sandbox Code Playgroud)

如果我想打印一个切片Foo,规范的方法可能是编写一个for循环,并有一个函数来封装它:

func printFoos(fs []Foo) {
    for _, f := range fs {
        f.print()
    }
}
printFoos([]Foo{})
Run Code Online (Sandbox Code Playgroud)

来自 OOP 背景,我发现这种方法有点不吸引人。

我想做的是printFoos[]Foo

// Invalid Go code
func (fs []Foo) print() {
    for _, f := range fs {
        f.print()
    }
}
Run Code Online (Sandbox Code Playgroud)

以上不起作用,因为在 Go 中,未命名类型不能用作方法接收器,如Google Group thread 中所述

为了规避它,可以写:

type Foos []Foo
func (fs Foos) print() {
    for _, f := range fs {
        f.print()
    }
}
Run Code Online (Sandbox Code Playgroud)

要使用它,我必须显式声明的类型Foos,所以我仍然无法使用print[]Foo

fs := []Foo{}
fs.print() // error
var fss Foos = fs
fss.print()
Run Code Online (Sandbox Code Playgroud)

我感到困惑的是,在上面的代码,fssfs显然是同类型的,因为我可以分配fsfss没有错误。但是,我们不能简单地使用fs.print()并让 Go 智能地进行转换。

为什么会这样?

完整代码可以在playground上找到。

icz*_*cza 7

我感到困惑的是,在上面的代码,fssfs显然是同类型的,因为我可以分配fsfss没有错误。

你跳到错误的结论。具有相同的类型并不是可分配性的必要条件。

fss有 typeFoosfstype []Foo,一种未命名的切片类型。确实,它们具有相同的基础类型,这就是为什么您可以分配fsfss,包含在此可分配性规则中:

的值x分配可变型的T(“x是分配给T”),如果满足下列条件中的一个适用:

  • ...
  • x的类型VT具有相同的基础类型,并且至少有一个VT不是已定义类型。

方法绑定到具体类型。因此,该Foos.print()方法不适用于不同类型的值,包括[]Foo.

但是您不需要创建一个变量来调用该方法,您可以简单地使用类型转换

Foos(fs).print()
Run Code Online (Sandbox Code Playgroud)

这种转换不改变内存布局,只改变类型,所以安全高效。我们仅使用它来访问具有相同基础类型的类型的方法。