Go any 和 interface{} 入门:新名字背后的老概念

本文讲解 Go 1.18 中 any 与 interface{} 的关系,说明空接口、泛型约束、类型断言和业务代码边界。

any 只是 interface{} 的别名

Go 1.18 引入泛型时,也引入了一个新预声明标识符:any。很多初学者会以为它是新类型,其实它只是:

type any = interface{}

也就是说,anyinterface{} 完全等价。它的出现主要是为了让泛型代码更易读:

func First[T any](items []T) (T, bool) {
	var zero T
	if len(items) == 0 {
		return zero, false
	}
	return items[0], true
}

这比写 [T interface{}] 更短,也更符合“没有额外约束”的语义。但在普通业务函数里,any 仍然意味着“我不知道具体类型”,使用时要谨慎。

空接口能接收任何值

func Print(v any) {
	fmt.Printf("%v\n", v)
}

调用:

Print("hello")
Print(123)
Print(User{Name: "小林"})

这适合日志、调试、通用格式化。但如果你写:

func Save(v any) error {
}

调用方不知道能保存什么,函数内部也必须自己判断类型。普通业务代码里,更好的方式是写明确类型:

func SaveUser(user User) error {
}

或者用有方法的小接口:

type Validatable interface {
	Validate() error
}

any 不是“更现代的类型安全”,它只是空接口的新名字。

类型断言仍然需要

func NameOf(v any) (string, error) {
	user, ok := v.(User)
	if !ok {
		return "", fmt.Errorf("value must be User")
	}
	return user.Name, nil
}

如果你省略 ok

user := v.(User)

类型不匹配时会 panic。除非你非常确定,否则使用 value, ok 更安全。

处理多个类型:

func Describe(v any) string {
	switch value := v.(type) {
	case string:
		return "string: " + value
	case int:
		return fmt.Sprintf("int: %d", value)
	case nil:
		return "nil"
	default:
		return fmt.Sprintf("unknown: %T", value)
	}
}

这和 interface{} 时代完全一样。any 没有改变运行时类型判断的规则。

泛型里的 any 更自然

泛型函数:

func Map[T any, R any](items []T, convert func(T) R) []R {
	result := make([]R, 0, len(items))
	for _, item := range items {
		result = append(result, convert(item))
	}
	return result
}

这里 any 表示 TR 没有额外要求。函数只是把元素交给 convert,不需要知道具体类型。

如果函数里要比较,就不能用 any

func Contains[T comparable](items []T, target T) bool {
	for _, item := range items {
		if item == target {
			return true
		}
	}
	return false
}

约束应该描述函数真正需要的能力。any 是最宽的约束,不是默认最好。

什么时候写 any,什么时候写 interface

在 Go 1.18 以后,新代码里通常可以这样取舍:

  • 泛型约束里表示任意类型,用 any
  • 普通函数参数里如果确实接收任意值,也可以用 any
  • 维护旧代码时看到 interface{} 不必强行改
  • 公开 API 大规模从 interface{} 改成 any 要谨慎,避免无意义 churn

比如:

func LogValue(key string, value any) {
}

可以接受。但不要把具体业务参数改宽:

func Register(input any) error {
}

这会让调用方失去类型帮助。

和 JSON 解码一起使用时要更谨慎

any 经常出现在未知 JSON 结构里:

var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
	return err
}

读取字段时需要断言:

name, ok := payload["name"].(string)
if !ok {
	return fmt.Errorf("name must be string")
}

数字尤其要注意。默认解码到 any 时,JSON number 会变成 float64

age, ok := payload["age"].(float64)

如果你需要精确整数,结构体通常更合适:

type UserPayload struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

只要 JSON 结构是你能提前定义的,就优先用结构体。map[string]any 适合动态字段、第三方事件 payload 或调试工具,不适合所有接口默认使用。

小结

anyinterface{} 的别名,语义上表示任意类型。它让泛型代码更易读,但没有改变空接口的本质。使用 any 后,类型断言、type switch、运行时判断这些规则仍然存在。

入门阶段要记住:能写明确类型就写明确类型,需要表达行为就写小接口,只有确实需要任意类型时才用 any。新名字不应该成为放弃类型安全的理由。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页