新闻详情

新闻详情

首页 / 资讯中心 / 详情

Erlang/OTP 记录(Records)实战指南:定义、创建、访问与编译期元组展开原理

发布时间:2026/9/25 4:47:37来源:尧图网络
Erlang/OTP 记录(Records)实战指南:定义、创建、访问与编译期元组展开原理
编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载Records 是 Erlang/OTP 中用于存储固定数量元素的命名数据结构其作用与 C 语言中的 struct 类似但所有 record 表达式都会在编译期被展开为元组tuple表达式。本文以 Erlang/OTP 参考手册 — Records 为核心系统讲解 record 的定义、创建、字段访问、更新、守卫guard与模式匹配用法并结合本仓库lib/compiler与lib/stdlib的源码深入剖析 record 的元组内部表示与编译器优化路径。读完本文你将能够熟练地在模块与 Shell 中定义和使用 records理解其底层代价并能在 ETS/Mnesia 等场景中借助 record 语法写出高效、可读的匹配代码。Records 是什么命名访问 vs 位置访问Record 是一种“定长、命名字段”的数据结构。与元组相比record 最大的优势在于按名字访问字段而元组只能按位置访问。考虑用元组{Name, Address, Phone}表示一个人若要提取数据只能依赖位置记忆Name element(1, P), Address element(2, P), ...这样的代码难以阅读一旦调整字段顺序或增删字段所有element/2调用都必须逐一核对与修改。而使用 record 后-record(person, {name, phone, address}). Name P#person.name, Address P#person.address, ...内部实现上record 就是一个带标签tag的元组例如{person, Name, Phone, Address}首元素是 record 名原子后面依次是各字段值。更完整的示例可参考 Programming Examples — Records。定义 Records-record属性与默认值Record 定义由 record 名和若干字段名组成record 名与字段名都必须是原子atom。每个字段可以带可选的默认值语法如下-record(Name, {Field1 [ Expr1], ... FieldN [ ExprN]}).要点若字段未提供默认值其默认值即为原子undefined。默认值可以是任意表达式但不能使用任何变量。Record 定义可以放在模块中任意属性与函数声明之间但必须在使用该 record 之前出现。若同一个 record 被多个模块使用推荐把定义放进.hrl头文件再用-include引入。例如-record(person, {name , phone [], address}).这里name默认phone默认[]而address未给默认值因此默认是undefined。变更说明从 Erlang/OTP 26 起可以在 Erlang Shell 中直接使用-record语法定义 record早期版本必须使用m:shell内建函数rd/2。Shell 下维护 record 定义的相关函数详见 STDLIB 的 shell 手册包括rd(RecordName, RecordDefinition)定义 record、rf()/rf(RecordNames)删除定义、rl()打印全部定义、rr(Module)从模块加载定义。需要注意的是每个 Shell job 各自维护一套 record 定义。创建 Records创建 record 的表达式如下其中每个FieldI的值由对应表达式ExprI求值得到#Name{Field1Expr1, ..., FieldKExprK}字段顺序可以任意不必与定义一致字段可以省略省略的字段取各自的默认值。示例 #person{phone[0,8,2,3,4,3,1,2], nameRobert}. #person{name Robert,phone [0,8,2,3,4,3,1,2],address undefined}特殊字段_批量设置所有未指定字段若希望多个字段获得同一个值可使用特殊字段_自 Erlang 5.1/OTP R8B 引入#Name{Field1Expr1, ..., FieldKExprK, _ExprL}此时省略的字段取ExprL的求值结果而非各自的默认值。该特性主要用于构造 ETS 与 Mnesia 的匹配模式-record(person, {name, phone, address}). lookup(Name, Tab) - ets:match_object(Tab, #person{nameName, __}).这里__把未显式指定的phone、address字段都设为原子_它正是ets:match/2、mnesia:match_object/3中的通配符。在 Shell 中演示 #person{name Jakob, _ _}. #person{name Jakob,phone _,address _}访问 Record 字段读取字段值Expr#Name.Field其中Expr求值结果必须是一个Namerecord表达式返回指定字段的值-record(person, {name, phone, address}). get_person_name(Person) - Person#person.name.获取字段在元组中的位置#Name.Field返回指定字段在 record 的元组表示中的位置下标从 1 开始位置 1 是 record 名本身。这个表达式是编译期常量常用于lists:keyfind/3等按位置操作的场景-record(person, {name, phone, address}). lookup(Name, List) - lists:keyfind(Name, #person.name, List).更新 RecordsExpr#Name{Field1Expr1, ..., FieldKExprK}Expr须求值为一个Namerecord。该表达式返回该 record 的一个副本其中指定字段被更新为新值其余字段保持原值 P1 #person{nameJoe, phone[1,2,3], addressA street}. P2 P1#person{nameRobert}. #person{name Robert,phone [1,2,3],address A street}注意P1本身并未被修改——Erlang 数据不可变更新 record 实际是构造新元组这一点由编译器的 copy 语义保证。Records 在 Guards 中的使用由于 record 表达式在编译期会被展开为元组表达式在 guard 中创建 record 与访问其字段是允许的但所有子表达式用于初始化字段的表达式本身也必须是合法的 guard 表达式handle(Msg, State) when Msg : #msg{tovoid, no3} - ... handle(Msg, State) when State#state.running : true - ...此外还有类型测试 BIFis_record(Term, RecordTag)用于在 guard 中判断一个值是否为某类 recordis_person(P) when is_record(P, person) - true; is_person(_P) - false.is_record/2的底层实现是检查“该元组首元素是否为给定原子、且元组大小是否匹配”。在编译器的类型分析中lib/compiler/src/beam_call_types.erl 中的is_record分支甚至会结合 record 定义推导出精确的元组形状从而生成更紧凑的运行时检查。标准库中这类用法非常普遍例如 lib/stdlib/src/dets.erl 大量使用is_record(H2, head)、is_record(NewHead, head)作为函数头 guard。Records 在模式匹配中的使用创建 record 的语法同样可以用于构造匹配模式#Name{Field1Expr1, ..., FieldKExprK}此时Expr1 ... ExprK中允许出现一个或多个未绑定变量。字段在模式中可以按任意顺序书写。典型例子是按名字查找电话find_phone([#person{nameName, phonePhone} | _], Name) - {found, Phone}; find_phone([_| T], Name) - find_phone(T, Name); find_phone([], Name) - not_found.也可以配合绑定 #person{name Name} P3, Name. Joe嵌套 RecordsRecord 的字段值本身可以是另一个 record。先看定义-record(nrec0, {name nested0}). -record(nrec1, {name nested1, nrec0#nrec0{}}). -record(nrec2, {name nested2, nrec1#nrec1{}}). N2 #nrec2{},访问或更新嵌套 record 时可以省略括号写成链式形式nested0 N2#nrec2.nrec1#nrec1.nrec0#nrec0.name, N0n N2#nrec2.nrec1#nrec1.nrec0#nrec0{name nested0a},它等价于显式加括号的写法nested0 ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name, N0n ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name nested0a},变更说明在 Erlang/OTP R14 之前访问或更新嵌套 record 时必须加括号。R14 起允许无括号的链式写法。内部表示编译期展开为元组Record 表达式在编译期被翻译为元组表达式。定义-record(Name, {Field1, ..., FieldN}).在内部被表示为元组{Name, Value1, ..., ValueN}其中每个ValueI是FieldI的默认值。也就是说-record(person, {name , phone [], address}).编译后对应的元组形状是{person, , [], undefined}。record_info/2 伪函数每个使用 record 的模块在编译期都会被注入一个获取 record 信息的伪函数record_info(fields, Record) - [Field] record_info(size, Record) - Sizerecord_info(fields, Record)返回字段名列表record_info(size, Record)返回元组表示的大小即字段数 1多出的 1 是 record 名所在位置。record_info/2的参数必须是编译期可知的字面量见 lib/compiler/doc/notes.md 中“record_info/2is a pseudo-function that requires literal arguments known at compile time”的说明。它在序列化、调试等代码中非常实用例如 lib/compiler/test/compile_SUITE_data/ssh.hrl 中就用record_info(fields, N)配合调试回调遍历 record 的所有字段。编译器视角从 record 语法到 BEAM 指令在本仓库的编译器中可以观察到 record 展开的完整踪迹lib/compiler/src/beam_asm.erl 定义了get_record_field的 BIF 类型bif_type(get_record_field, 3) - {op,get_record_field}并在汇编阶段把 record 定义编码进 BEAM 文件的native_record块build_record_chunk/2、build_record_def/2lib/compiler/src/beam_core_to_ssa.erl 将is_record、is_native_record、get_record_field等 record 相关操作转换为 SSA 形式的中间指令internal_cg分支中对is_record的处理会先取元组大小再与期望值比较lib/compiler/src/beam_block.erl 中的opt_records/1与simplify_get_record_elements_id/5会对get_record_elements_id指令做块级优化例如当只需要提取单个字段时避免为整条 record 复制多个元素。由此可以推断record 语法几乎不带来运行时开销——字段访问最终被优化为按位置读取元组元素record 名字与字段位置在编译期就已解析完毕。这也是为什么标准库源码如 lib/stdlib/src/dets.erl、lib/stdlib/src/array.erl大量使用 record 来表达内部状态结构。实战综合示例结合以上语法给出一个完整的模块示例摘自 Programming Examples — Records。首先是把 record 定义放入头文件供多模块共享%% File: person.hrl %% Data Type: person %% where: %% name: A string (default is undefined). %% age: An integer (default is undefined). %% phone: A list of integers (default is []). %% dict: A dictionary containing various information %% about the person. %% A {Key, Value} list (default is the empty list). -record(person, {name, age, phone [], dict []}).然后是使用该 record 的模块涵盖创建、模式匹配、类型测试、字段访问与更新-module(person). -include(person.hrl). -compile(export_all). % For test purposes only. %% 创建实例。phone 未提供使用默认值 []。 make_hacker_without_phone(Name, Age) - #person{name Name, age Age, dict [{computer_knowledge, excellent}, {drinks, coke}]}. %% 在参数中进行 record 模式匹配 print(#person{name Name, age Age, phone Phone, dict Dict}) - io:format(Name: ~s, Age: ~w, Phone: ~w ~n Dictionary: ~w.~n, [Name, Age, Phone, Dict]). %% 类型测试 字段访问 更新 birthday(P) when is_record(P, person) - P#person{age P#person.age 1}. register_two_hackers() - Hacker1 make_hacker_without_phone(Joe, 29), OldHacker birthday(Hacker1), central_register_server ! {register_person, Hacker1}, central_register_server ! {register_person, OldHacker#person{name Robert, phone [0,8,3,2,4,5,3,1]}}.这段代码展示了 records 的全部核心能力#person{...}创建、print/1参数模式匹配、is_record(P, person)守卫类型测试、P#person.age字段访问以及P#person{age ...}更新。小结与最佳实践优先使用 record 而非裸元组按名字访问字段可读性强字段增删改时无需逐个修正element/2下标。默认值不给默认值的字段为undefined默认值表达式不能使用变量。多模块共享定义将-record放入.hrl头文件并用-include引入。定义须在使用之前无论模块内还是 Shell 中OTP 26 起 Shell 支持-record语法早期版本用rd/2。_特殊字段为 ETS/Mnesia 匹配构造“其余字段全为_通配”的模式。编译期零运行时开销record 在编译期展开为元组record_info(fields|size, Record)提供字段名与大小信息字段访问会被编译器优化为按位置读取可参考 beam_asm.erl、beam_core_to_ssa.erl、beam_block.erl 的实现。嵌套链式访问OTP R14 起无需括号匹配/守卫场景中is_record/2是标准类型测试手段。若需要更多用法示例含 Shell 交互演示与 ETS 匹配模式的完整代码可继续阅读 Programming Examples — Records 与 STDLIB shell 手册。赞分享编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载相关推荐GKE 生产化就绪审查指南gke-productionize 技能的多领域编排评估与 RAG 评分实战GKE 生产化就绪审查指南gke productionize 技能的多领域编排评估与 RAG 评分实战 导读 gke productionize 是本仓库 s编程语言语言运行时标准库编译器并发编程在 Android 与 Raspberry Pi 上交叉编译 Erlang/OTP完整实战指南在 Android 与 Raspberry Pi 上交叉编译 Erlang/OTP完整实战指南 本指南基于 Erlang/OTP 官方仓库的 HOWTO/IN编程语言语言运行时标准库编译器并发编程Erlang/OTP 交叉编译完全指南基于 otp_build 与 configure 的跨平台构建实战Erlang/OTP 交叉编译完全指南基于 otp_build 与 configure 的跨平台构建实战 本篇指南以 Erlang/OTP 官方交叉编译文档编程语言语言运行时标准库编译器并发编程上一篇实战指南基于OpenVINO的Audacity AI插件开发与部署下一篇Expanding Collection实战5个高级定制技巧让你的iOS应用脱颖而出创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Ariakit VisuallyHidden 组件完整指南:视觉隐藏与无障碍文本的最佳实践 2026/9/25 5:47:23

Ariakit VisuallyHidden 组件完整指南:视觉隐藏与无障碍文本的最佳实践

UI组件前端 【免费下载链接】ariakit Toolkit with accessible components, styles, and examples for your next web app 项目地址: https://gitcode.com/gh_mirrors/ar/ariakit 点击查看 免费下载 导读 VisuallyHidden 是 Ariakit 提供的一个基础无障碍&#xf…

阅读更多 →
利用AI大模型增强DevOps:TaoToken统一Key接入Cline与CC Switch的配置实战 2026/9/25 5:47:23

利用AI大模型增强DevOps:TaoToken统一Key接入Cline与CC Switch的配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Erlang/OTP NIF 实战指南:从 C 到 Erlang 的原生函数调用 2026/9/25 5:47:23

Erlang/OTP NIF 实战指南:从 C 到 Erlang 的原生函数调用

编程语言语言运行时标准库编译器并发编程 【免费下载链接】otp Erlang/OTP 项目地址: https://gitcode.com/gh_mirrors/ot/otp 点击查看 免费下载 本文是 Erlang/OTP 互操作教程中 NIF(Native Implemented Functions) 部分的完整实战指南。N…

阅读更多 →
体育电竞比分网SEO:结构化数据与毫秒级性能实战指南 2026/9/25 5:47:23

体育电竞比分网SEO:结构化数据与毫秒级性能实战指南

1. 为什么体育和电竞比分网的SEO是“反常识”的硬骨头?你刚接手一个体育比分网站,首页堆满实时赛果、赔率跳动、直播链接,流量却卡在日均3000UV不动弹;或者你运营着一个专注《英雄联盟》《DOTA2》《CS2》赛事的垂直电竞比分平台&a…

阅读更多 →
BAML C 桥接层 ProtocolProbe 探针:确定性 Protobuf 生成与下游包隔离的验证机制 2026/9/25 5:47:23

BAML C 桥接层 ProtocolProbe 探针:确定性 Protobuf 生成与下游包隔离的验证机制

编程语言AI Agent编译器CLI人工智能 【免费下载链接】baml The programming language for agents 项目地址: https://gitcode.com/gh_mirrors/ba/baml 点击查看 免费下载 本篇文章聚焦 BAML 仓库中一个容易被忽略但技术含量极高的测试基础设施:位于 bam…

阅读更多 →
CKS32F103C8T6替代STM32F103C8T6:Keil工程迁移与调试避坑指南 2026/9/25 5:47:16

CKS32F103C8T6替代STM32F103C8T6:Keil工程迁移与调试避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉