当前位置:首页 > 文章列表 > Golang > Go教程 > Go 泛型自引用约束怎样表达递归接口

Go 泛型自引用约束怎样表达递归接口

来源:17golang原创 2026-10-26 17:34:46 0浏览 收藏

如果一个泛型函数要求类型 A 提供 Add(A) A,你现在可以直接把这层关系写进约束接口:type Adder[A Adder[A]] interface { Add(A) A }。这类“自引用”描述的是类型参数和方法参数之间的关系,不是链表、树那样的递归数据结构;该声明从 Go 1.26 起才被语言规则放开。

要点速览
  • Adder[A] 的核心含义是:实现类型必须能接收同一个类型 A,并返回 A。
  • 示例项目用 Score 实现 Add(Score) Score,泛型函数就能保持输入和输出的具体类型。
  • Go 1.25 及更早版本会拒绝这种在泛型接口自身参数列表中的自引用写法,方法签名不一致的类型也不会满足约束。

先看清“递归接口”究竟递归在哪里

这里的递归不是接口里嵌套自己,也不是运行时对象指向自己。递归关系只出现在类型约束表达式中:接口 Adder[A] 接收类型参数 A,而 A 又被要求满足 Adder[A]。接口的方法把同一个 A 放在参数和返回值位置,于是泛型函数可以保留具体类型。

Go 官方在 1.26 发布说明中给出的关键形状就是下面这样。先记住它表达的关系,再把它放进一个完整的小项目:

// Adder 要求实现类型的 Add 方法接收并返回同一种类型 A。
type Adder[A Adder[A]] interface {
    Add(A) A
}
Go 泛型自引用约束中类型参数、Adder[A] 与 Score 实现之间的静态关系图
图1:类型参数、Adder[A] 约束和 Score 实现之间的关系说明,不是 IDE 或运行截图。

用一个小项目固定 Go 版本和目标

这个写法依赖 Go 1.26 的语言变化,因此先在项目根目录固定语言版本。这里不需要额外依赖,项目只包含标准 Go 代码。

# 创建示例目录,并把语言版本固定到支持自引用约束的 Go 1.26。
mkdir -p recursive-adder
cd recursive-adder
go mod init example.com/recursive-adder

# go.mod 中的 go 行应保持为 1.26 或更高版本。
go mod edit -go=1.26

go 行表示项目按哪个语言版本解释代码。它不是把本机工具链自动升级到 1.26;如果本机仍是更早版本,应该先更换工具链,再继续这个示例。

声明自引用约束并实现具体类型

接着定义一个整数分值类型。Score 的 Add 方法必须精确接收和返回 Score;如果把参数写成 int,即使底层类型也是整数,也不再是同一个方法签名。

package main

import "fmt"

// Adder 把类型参数 A 同时放进约束和 Add 方法签名。
type Adder[A Adder[A]] interface {
    Add(A) A
}

// Score 是满足 Adder[Score] 的具体值类型。
type Score int

// Add 接收 Score 并返回 Score,保持类型关系闭合。
func (s Score) Add(other Score) Score {
    return s + other
}

// combine 只依赖 Adder[A] 的 Add 能力,不需要知道 A 的底层类型。
func combine[A Adder[A]](left, right A) A {
    return left.Add(right)
}

func main() {
    // 类型参数可以从两个 Score 参数中推断出来。
    total := combine(Score(18), Score(24))
    fmt.Println(total)
}

这里的关键不是把 Score 转成接口,而是让编译器确认 Score 满足 Adder[Score]。因此 combine 返回的仍然是 Score,调用方不需要手工断言。

从调用结果判断约束是否生效

运行项目后,输出是一个普通的数值结果:

42

更重要的判断点有两个:第一,combine 没有写死 Score,却能调用 Add;第二,返回值仍保持 A,所以后续代码可以继续使用 Score 的方法,而不必从普通接口值中取回具体类型。

如果新增另一个类型,只要它实现同形状的方法,就能复用同一个泛型函数:

// Distance 也把输入和输出保持为同一个具体类型。
type Distance float64

// Add 的参数和返回值都必须是 Distance。
func (d Distance) Add(other Distance) Distance {
    return d + other
}

// 这里仍由编译器推断 A 为 Distance。
var routeLength Distance = combine(Distance(1.5), Distance(2.25))

三类边界最容易混淆

把下面三种情况分开,基本就能判断这条约束是否写对:

情况结果原因
Go 1.26+,Score.Add(Score) Score满足自引用约束和方法签名都成立
Go 1.25 及更早,声明 Adder[A Adder[A]]声明阶段被拒绝旧语言规则不允许泛型类型在参数列表中引用自身
任意版本,BadScore.Add(int) int不满足方法参数不是同一个具体类型 BadScore
Go 1.26 自引用约束与旧版本限制及不匹配方法签名的静态边界图
图2:版本边界和方法签名边界说明,不是编译器日志或运行证据。

可以用下面的错误示例理解最后一种边界:

// BadScore 的方法没有保持 BadScore 这一具体类型关系。
type BadScore int

// 参数和返回值都是 int,因此不能满足 Adder[BadScore]。
func (s BadScore) Add(other int) int {
    return int(s) + other
}

// 这行会因为方法集合不匹配而无法通过类型约束。
// var _ Adder[BadScore] = BadScore(1)

最后一行用类型断言式的赋值检查约束,但故意保留为注释,避免把错误示例混进可运行主程序。真正排查时,优先比较方法的接收者、参数和返回值三处类型。

什么时候该使用这种写法

当算法需要“输入类型和返回类型始终是同一个实现类型”时,自引用约束能让接口声明更贴近这个意图,例如数值累加器、同类型合并器或返回同类型副本的值对象。它适合表达静态类型关系,不负责创建递归对象,也不替代运行时的递归终止条件。

  • 先确认项目确实以 Go 1.26 或更高版本编译。
  • 检查方法签名是否严格使用同一个具体类型,不要只看底层类型是否相同。
  • 如果只需要“有一个方法”而不要求输入输出同型,普通泛型接口或更宽的约束更容易理解。
  • 如果问题是树、链表或 AST 的节点递归,应单独设计数据结构,不要把它误写成类型约束问题。

常见问题

这是不是 Go 的递归数据结构?

不是。递归只发生在约束声明的类型参数关系里;示例中的 Score 仍是普通数值类型,没有指向自身的字段。

为什么 Add(int) int 不能满足 Adder[BadScore]?

因为约束实例化后要求的是 Add(BadScore) BadScore。底层类型相同不等于方法签名相同,Go 不会自动把这两个签名视为可替换。

旧版本有什么直接替代写法?

可以把接口参数化但不在接口自己的参数列表中引用接口自身,再让泛型函数使用它;不过本文的直接声明形式依赖 Go 1.26,升级工具链后再采用更清晰的写法。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go //go:fix inline 怎样为 API 迁移准备内联规则Go //go:fix inline 怎样为 API 迁移准备内联规则
上一篇
Go //go:fix inline 怎样为 API 迁移准备内联规则
Go new 表达式怎样直接初始化带默认值指针
下一篇
Go new 表达式怎样直接初始化带默认值指针
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    713次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    716次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    798次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    758次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    589次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码