尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Gleam 运行时错误完全指南:todo、panic、let assert 与 assert 的跨后端错误格式

Gleam 运行时错误完全指南:todo、panic、let assert 与 assert 的跨后端错误格式 Gleam 运行时错误完全指南todo、panic、let assert 与 assert 的跨后端错误格式【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleamGleam 编译器本仓库即其官方实现为所有失败类代码路径todo、panic、let assert、assert定义了统一的运行时错误约定无论目标是 Erlang 还是 JavaScript抛出的异常都携带结构化的元数据字段用于定位源码位置、区分错误类型并辅助诊断。读完本文你将掌握每种运行时错误的触发方式、在 Erlang/JavaScript 两端的确切数据格式包括所有字段名与类型、assert针对不同表达式形态的差异化错误结构以及如何在代码中捕获并解读这些错误。运行时错误的统一约定Gleam 是一门默认不可失败的语言普通的case匹配必须是穷尽的所有可能失败的分支都必须显式处理。但当程序确实需要在运行时放弃执行时Gleam 提供了todo、panic、let assert和assert四种语法结构。它们抛出的错误并不是随意的异常而是遵循一份由编译器保障的结构化契约记录在仓库的 docs/runtime-errors.md 中并被两套代码生成后端同时实现。在 Erlang 目标上运行时错误是通过erlang:error/1抛出的 Erlang Map在 JavaScript 目标上则是带有附加属性的 JavaScriptError实例。两份实现的共同点在于错误对象上至少携带以下定位与分类字段方便日志系统、错误监控与调试工具做统一处理。Erlang 端的公共字段字段 Key类型含义gleam_errorAtom错误类别标识取值见各错误小节messageString错误消息取值见各错误小节fileString出错代码对应的原始源文件路径moduleString出错时所在的模块名functionString出错时所在的函数名lineInt出错代码在源文件中的行号JavaScript 端的公共字段字段 Key类型含义gleam_errorString错误类别标识取值见各错误小节messageString错误消息取值见各错误小节moduleString出错时所在的模块名functionString出错时所在的函数名lineNumber出错代码在源文件中的行号注意一个细节Erlang 端多出file字段因为 Erlang 的 map 需要显式填充所有信息而 JavaScript 端file同样存在见下文源码文档表格只列出了至少有的字段实际实现中两端的字段集会更为丰富。Erlang 后端erlang:error/1与错误 Map 的生成Erlang 端错误 Map 的生成逻辑集中在 compiler-core/src/erlang.rs 的start_runtime_error函数约 L1194-L1250。该函数先开启一个对erlang:error/1的远端调用再开始构造错误 Map随后按固定顺序写入公共字段gleam_error根据RuntimeErrorKind枚举写入todo/panic/assert/let_assert之一对应源码 L1208-L1213 的匹配分支message若用户用as ...提供了自定义消息则取该表达式否则使用该错误类别的默认消息L1216-L1222file取模块生成器记录的module_source_pathL1224-L1226module取当前模块名L1228-L1230function取当前正在生成代码的函数名L1232-L1234line通过行号表把location.start字节偏移换算为行号L1236-L1244。start_runtime_error之后必须调用end_runtime_errorL1252-L1260来闭合 map 与调用中间可以插入额外的map_field写入各类错误独有的附加字段——这正是下文assert不同形态能携带差异化信息的基础。JavaScript 后端Error实例与makeErrorJavaScript 端并不直接在每个错误点手写new Error(...)而是复用编译器模板中定义的makeError辅助函数位于 compiler-core/templates/prelude.mjsL1594-L1605export function makeError(variant, file, module, line, fn, message, extra) { let error new globalThis.Error(message); error.gleam_error variant; error.file file; error.module module; error.line line; error.function fn; // TODO: Remove this with Gleam v2.0.0 error.fn fn; for (let k in extra) error[k] extra[k]; return error; }它把公共字段逐一挂到Error实例上再用for (let k in extra)将额外字段如kind、value、start、end等展开写入错误对象。编译器在 compiler-core/src/javascript.rsL257 附近注册对makeError的 prelude 引用生成的代码形如import { makeError } from ../gleam.mjs; // ... throw makeError( assert, FILEPATH, my/mod, 4, main, Assertion failed., { kind: binary_operator, operator: ||, /* ... */ } )以上实际输出可见于快照文件 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operation.snap它由测试 compiler-core/src/javascript/tests/assert.rs 驱动生成。Todo标记未完成的代码todo表示这段代码还没有写完是开发期使用的占位符一旦执行到就会抛出运行时错误。它既可以直接使用也可以用as附带自定义消息todo todo as some message字段 KeyErlang 值JavaScript 值gleam_errortodotodomessage给定的消息给定的消息Erlang 端TypedExpr::Todo的代码生成分支位于 compiler-core/src/erlang.rsL1081-L1091它直接调用start_runtime_error(RuntimeErrorKind::Todo, ...)JavaScript 端则生成对makeError(todo, ...)的调用。仓库中 compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__todo__plain.snap、compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__todo__todo_as.snap 等快照记录了生成的 Erlang 代码。若不提供消息Erlang 端会回退到RuntimeErrorKind::Todo的默认错误消息。Panic无条件终止panic用于此处绝不应继续执行的显式终止语义与todo类似但意图不同todo表示尚未实现panic表示主动放弃panic panic as some message字段 KeyErlang 值JavaScript 值gleam_errorpanicpanicmessage给定的消息给定的消息Erlang 端的生成分支在 compiler-core/src/erlang.rsL1092-L1102对应快照包括 compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__panic__plain.snap 与 compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__panic__panic_as.snap。Let assert不穷尽的模式匹配let assert是一种断言式绑定如果右侧表达式与左侧模式不匹配程序立即出错如果匹配则正常绑定变量并继续。它是把不穷尽匹配转化为显式运行时检查的唯一途径let assert Ok(x) something() let assert Error(e) something() as This should fail字段 KeyErlang 值JavaScript 值gleam_errorlet_assertlet_assertmessage给定的消息给定的消息value未匹配上的原始值未匹配上的原始值startlet assert语句起始处的字节索引let assert语句起始处的字节索引endlet assert语句结束处的字节索引let assert语句结束处的字节索引pattern_start被断言模式起始处的字节索引被断言模式起始处的字节索引pattern_end被断言模式结束处的字节索引被断言模式结束处的字节索引Erlang 端的实现compiler-core/src/erlang.rs 的let_assert函数L1288-L1387揭示了其底层机制当模式必然匹配pattern.always_matches()如普通变量、元组模式时let assert被降级为普通let完全不产生错误分支否则生成一个case表达式——匹配成功的分支继续执行后续语句若let assert是块内最后一条语句则直接返回匹配值另有一个兜底分支把被匹配值绑定到变量后抛错。附加字段正是在这个兜底分支里写入的L1363-L1381value兜底分支绑定的未匹配值start/end整个let assert语句的源码字节区间pattern_start/pattern_end被断言模式的源码字节区间。注意这些索引是字节索引而非行号可用于在编辑器中精确定位到表达式。Assert布尔断言的差异化错误assert用于断言某个布尔表达式为真。与其他三类错误不同它的错误格式取决于被断言的表达式形态因此诊断信息远比一条字符串消息丰富。所有assert错误都固定包含以下字段字段 KeyErlang 值JavaScript 值gleam_errorassertassertmessage给定的消息给定的消息kind被断言表达式的种类被断言表达式的种类startassert语句起始处的字节索引assert语句起始处的字节索引endassert表达式结束处的字节索引assert表达式结束处的字节索引expression_start被断言表达式起始处的字节索引被断言表达式起始处的字节索引Erlang 端assert的整体处理在 compiler-core/src/erlang.rs 的assert函数L1468-L1687它对被断言的表达式做了一次分类分发match value三种形态分别生成三种错误。断言二元运算符kind binary_operatorassert level 30字段 KeyErlang 类型JavaScript 类型值kindAtomStringbinary_operatoroperatorAtomString使用的二元运算符leftMapObject运算符左侧的操作数描述rightMapObject运算符右侧的操作数描述Erlang 端由assert_binary_operator_errorL1840-L1880生成。源码显示L1484-L1579为了在错误中携带两侧操作数的运行时值编译器先把非简单变量的左右操作数绑定到生成变量再用case判断运算结果为true则断言通过返回nil为false则抛错。操作数本身按下一节所述的表达式描述 Map编码进left/right字段。断言函数调用kind function_callassert check_some_property(a, b, c)字段 KeyErlang 类型JavaScript 类型值kindAtomStringfunction_callargumentsList of mapArray of objects被断言函数的实参描述列表Erlang 端由assert_call_errorL1918-L1953生成。关键设计L1581-L1634每个非变量的实参都会被预先求值并绑定到生成变量避免断言失败时重复求值产生副作用随后这些实参的表达式描述以列表形式写入arguments字段供开发者逐一检查每个实参的运行时值。断言其他表达式kind expressionassert other_expression字段 KeyErlang 类型JavaScript 类型值kindAtomStringexpressionexpressionMapObject被断言表达式的运行时值描述Erlang 端由assert_expression_errorL1884-L1916生成。既然断言失败说明该表达式求值结果必为false因此只记录表达式本身的值与位置由expression字段承载。表达式描述 Map 的结构无论出现在left、right、arguments还是expression字段中每个被断言表达式的描述都是同一结构字段 KeyErlang 类型JavaScript 类型值valueanyany表达式在运行时求出的值kindAtomStringliteral或expression或unevaluatedstartIntNumber该表达式在源码中起始处的字节索引endIntNumber该表达式在源码中结束处的字节索引Erlang 端由assertion_expression_mapcompiler-core/src/erlang.rs L1958-L1998生成其核心语义是literal表达式是字面量例如True或15。它的值不依赖运行时状态错误报告时可以不必打印其值值恒为源码所见expression表达式是普通求值表达式value字段是它在运行时真实求出的值unevaluated表达式没有被求值通常因为它位于短路运算符||/的右侧且运算符提前短路。此时没有valueruntime_value.is_none()时源码 L1983 会跳过value字段的写入。短路求值对断言错误的影响与||在 Gleam 中是短路运算符这给断言错误带来了一个微妙约束错误发生时短路另一侧的表达式从未被求值因此其value不可得。Erlang 端为此实现了两套特殊生成逻辑assert_andcompiler-core/src/erlang.rs L1715-L1785手工展开短路逻辑。若左侧为false立即失败并把右侧标记为unevaluated若左侧为true而右侧为false则左侧记录为已知值true、右侧记录为false全程不重复求值任何一侧assert_orL1795-L1836由于||只有两侧都为false才会失败此时两侧都被求值过且都确定为false因此直接复用 Erlang 的orelse作为case主体错误中两侧都记录为false。JavaScript 端同样遵循该语义快照 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operation.snap 中assert x || False生成的错误对象即包含left: { kind: expression, value: false, ... }与right: { kind: literal, value: false, ... }两侧值在编译期即可确定。在宿主代码中捕获与解读错误的结构化设计是为了让宿主运行时能够统一捕获、记录与诊断。由于错误对象结构在两端是稳定的契约可以按gleam_error字段分流处理在 Erlang / Elixir 宿主中可用 Erlang 标准的try ... catch捕获erlang:error/1抛出的异常Gleam 本身不提供try但错误由 BEAM 的异常机制承载try Mod:fun() catch error:Map when is_map(Map), is_atom(maps:get(gleam_error, Map, undefined)) - %% Map 含 gleam_error / message / file / module / function / line 及各类附加字段 ... end在 JavaScript / TypeScript 宿主中直接捕获并读取属性即可try { mod.main(); } catch (error) { if (error instanceof Error gleam_error in error) { console.log(error.gleam_error); // todo | panic | let_assert | assert console.log(error.message, error.module, error.function, error.line); console.log(error.kind); // assert 专用如 binary_operator | function_call | expression } }运行时错误的源码验证路径汇总如果你希望在仓库中深入验证以上行为推荐按以下路径查阅错误契约文档docs/runtime-errors.md本文的原始依据含全部字段表格Erlang 后端实现compiler-core/src/erlang.rs重点看start_runtime_error/end_runtime_errorL1194-L1260、let_assertL1288-L1387、assert及其三类错误生成L1468-L1998JavaScript 运行时辅助compiler-core/templates/prelude.mjs 的makeErrorL1594-L1605Erlang 生成结果快照目录 compiler-core/src/erlang/snapshots 下的todo__*、panic__*、assert__*、let_assert__*系列快照JavaScript 生成结果快照compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operation.snap 及 compiler-core/src/javascript/tests/assert.rs 驱动的一组断言测试类型检查侧约束let assert与assert的编译期检查逻辑散见于 compiler-core/src/type_/tests/let_assert.rs、compiler-core/src/type_/tests/assignments.rs 及其快照用于保证这些构造在类型层面上的合法用法。理解这套错误契约的价值在于无论你的 Gleam 程序最终部署在 BEAM 还是 JS 运行时上你都可以依赖同一套gleam_error分类与字节级定位字段构建统一的错误监控、日志聚合与源码定位工具链而不必为不同后端分别适配。【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表