Go 中函数回调返回接口实现的正确方式

在 go 中,函数回调需返回接口类型而非具体结构体指针;因 go 的类型系统要求函数签名严格匹配,即使结构体实现了接口,func(string) *t 与 func(string) interface 仍被视为不同类型,无法直接赋值。

Go 的接口是静态声明、动态实现的抽象机制,但其类型系统对函数签名具有严格的类型身份(type identity)要求。问题核心在于:AddTextureLoader 期望接收一个 func(string) *Texture 类型的参数(注意:此处 *Texture 是指向接口的指针,本身即错误用法),而 NewDDSTexture 返回的是 *DDSTexture —— 一个具体结构体指针。尽管 *DDSTexture 可隐式转换为 Texture 接口值,但 func(string) *DDSTexture 和 func(string) Texture(或 func(string) *Texture)在类型层面完全不兼容。

✅ 正确做法:函数返回接口类型,而非结构体指针

首先,修正 resource.go 中的函数签名,将参数类型从 func(string) *Texture 改为 func(string) Texture:

// resource/resource.go
package resource

var (
    tex_types map[string]func(string) Texture = make(map[string]func(string) Texture)
)

type Texture interface {
    Texture() (uint32, error)
    Width() int
    Height() int
}

func AddTextureLoader(ext string, fn func(string) Texture) {
    tex_types[ext] = fn
}
⚠️ 注意:*Texture 是反模式!接口本身已是引用类型,*Texture 表示“指向接口变量的指针”,通常毫无必要且易引发混淆和内存误用。

接着,在 dds.go 中,让工厂函数返回 Texture 接口值(而非 *DDSTexture):

// texture/dds.go
package texture

import "your-module/resource"

type DDSTexture struct {
    path   string
    _tid   uint32
    height uint32
    width  uint32
}

func NewDDSTexture(filename string) resource.Texture {
    return &DDSTexture{
        path:   filename,
        _tid:   0,
        height: 0,
        width:  0,
    }
}

func (d *DDSTexture) Texture() (uint32, error) { /* 实现 */ }
func (d *DDSTexture) Width() int              { return int(d.width) }
func (d *DDSTexture) Height() int            { return int(d.height) }

func init() {
    resource.AddTextureLoader("dds", NewDDSTexture)
}

✅ 此时 NewDDSTexture 类型为 func(string) resource.Texture,与 AddTextureLoader 参数签名完全一致,编译通过。

? 关键原理说明

  • Go 不支持“协变返回类型”(covariant return types):func() *DDSTexture ≠ func() Texture,即使 *DDSTexture 实现了 Texture。
  • 接口值本质是 (type, value) 对,可安全持有任何满足接口的实例(包括 *DDSTexture);因此工厂函数应直接返回该接口值。
  • 使用 &DDSTexture{} 而非 DDSTexture{} 通常更合理(避免拷贝大结构体,且方法集完整),但返回类型必须是 Texture,而非 *DDSTexture。

? 总结

错误写法 正确写法
func(string) *Texture(接口指针) func(string) Texture(接口值)
func(string) *DDSTexture func(string) Texture(由 *DDSTexture 隐式转换)
AddTextureLoader("dds", NewDDSTexture)(类型不匹配) ✅ 类型一致,可直接注册

遵循这一模式,即可灵活注册任意 Texture 实现(如 PNGTexture、JPEGTexture),同时保持资源系统完全解耦于具体实现细节。