← 类型约束 | 反射 →

类型推断 - 泛型推断详解

类型推断允许编译器自动推导泛型类型参数,使代码更简洁。理解类型推断规则是编写优雅泛型代码的关键。

📌 核心概念

🔍

参数推断

从实参推导

自动推导
📝

显式指定

手动指定类型

[T int]
⚠️

推断失败

需要显式指定

编译错误
🆕

Go 1.22+

推断能力增强

新特性

基础类型推断

📝 从函数参数推断

package main

import "fmt"

// 简单推断:从参数类型推导
func Identity[T any](v T) T {
    return v
}

func main() {
    // ✅ 类型推断:T 推断为 int
    a := Identity(42)
    
    // ✅ 类型推断:T 推断为 string
    b := Identity("hello")
    
    // ✅ 显式指定类型
    c := Identity[int](100)
    
    fmt.Println(a, b, c)
}

// 多参数推断
func Pair[T1, T2 any](a T1, b T2) (T1, T2) {
    return a, b
}

func testPair() {
    // ✅ T1=int, T2=string
    x, y := Pair(1, "one")
    
    // ✅ 部分推断 + 显式指定
    // z, w := Pair[string, int]("two", 2)
}

类型推断规则

📖 推断成功与失败

package main

import "fmt"

// ✅ 推断成功:从参数推导
func Wrap[T any](v T) []T {
    return []T{v}
}

// ❌ 推断失败:没有参数可供推断
func MakeSlice[T any](n int) []T {
    return make([]T, n)
}

// ✅ Go 1.22+: 从返回类型推断
func First[T any](slice []T) T {
    return slice[0]
}

func main() {
    // ✅ 从参数推断
    w := Wrap(42)
    
    // ❌ 推断失败:必须显式指定
    // s := MakeSlice(10)  // 编译错误!
    s := MakeSlice[int](10) // ✅ 显式指定
    
    // ✅ 从切片参数推断
    nums := []int{1, 2, 3}
    f := First(nums)
    
    fmt.Println(w, s, f)
}

💡 推断规则总结

  • 参数推断: 从函数实参类型推导类型参数
  • 无参数失败: 没有类型参数相关的参数时推断失败
  • 部分推断: 可以部分推断 + 部分显式指定
  • Go 1.22+: 增强了从返回类型推断的能力

复杂推断场景

📝 多类型参数推断

package main

import "fmt"

// Map: 从 slice 和 fn 推断 T 和 U
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Filter: 从 slice 推断 T
func Filter[T any](slice []T, predicate func(T) bool) []T {
    result := []T{}
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce: 从 slice 和 initial 推断 T 和 R
func Reduce[T, R any](slice []T, initial R, fn func(R, T) R) R {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    
    // ✅ T=int, U=string 自动推断
    strs := Map(nums, func(n int) string {
        return fmt.Sprintf("num-%d", n)
    })
    
    // ✅ T=int 自动推断
    evens := Filter(nums, func(n int) bool {
        return n%2 == 0
    })
    
    // ✅ T=int, R=int 自动推断
    sum := Reduce(nums, 0, func(acc, n int) int {
        return acc + n
    })
    
    fmt.Println(strs, evens, sum)
}

推断技巧

📝 帮助类型推断

package main

import "fmt"

// 技巧 1: 添加辅助参数帮助推断
func NewMap[K, V any](key K, value V) map[K]V {
    m := make(map[K]V)
    m[key] = value
    return m
}

// 技巧 2: 使用类型别名简化
type StringFunc func(string) string

func Process(fn StringFunc, input string) string {
    return fn(input)
}

// 技巧 3: 提供非泛型版本
func NewIntSlice() []int {
    return make([]int, 0)
}

func main() {
    // ✅ 帮助推断
    m := NewMap("key", "value")
    
    // ✅ 类型别名
    result := Process(func(s string) string {
        return s + "!"
    }, "hello")
    
    // ✅ 非泛型版本
    ints := NewIntSlice()
    
    fmt.Println(m, result, ints)
}

常见陷阱

⚠️ 推断失败场景

// 陷阱 1: 没有参数可供推断
func Empty[T any]() []T {
    return []T{}
}
// Empty()  // ❌ 推断失败
// Empty[int]()  // ✅ 必须显式指定

// 陷阱 2: 参数类型不匹配
func Compare[T comparable](a, b T) bool {
    return a == b
}
// Compare(1, "one")  // ❌ 类型不匹配

// 陷阱 3: 约束太宽泛
func Process[T any](v T) T {
    // 无法对 T 做任何操作
    return v
}

// 陷阱 4: 返回值依赖推断
func Create[T any](n int) []T {
    return make([]T, n)
}
// Create(5)  // ❌ 推断失败
// Create[int](5)  // ✅ 显式指定

最佳实践

✅ 类型推断使用建议

  • 优先推断: 让编译器推断类型,代码更简洁
  • 设计参数: 设计函数时考虑推断友好性
  • 显式指定: 推断失败时显式指定类型
  • 清晰命名: 类型参数使用 T、K、V 等通用名称