资讯动态

Gleam 跨目标编译实战:用 @external 外部函数构建仅支持 Erlang 的模块

发布时间:2026/9/13 16:20:30 来源:尧图企业网站定制
Gleam 跨目标编译实战用 external 外部函数构建仅支持 Erlang 的模块【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam导读Gleam 是一门可同时编译到 Erlang 虚拟机BEAM与 JavaScript 运行时的强类型语言但并非所有代码都能在两个目标上无缝运行——当你在.gleam源码中通过external属性接入某个平台专属的原生实现如 Erlang 模块、JavaScript 模块时该函数就只对声明的那一个编译目标可用。本文以仓库中的 test/external_only_erlang 集成测试项目为样本完整讲解仅 Erlang 可用的外部函数项目的结构、编译行为与错误机制并对照 test/external_only_javascript 给出 JavaScript 侧的镜像案例帮助你理解 Gleam 外部函数External Function / FFI的跨目标约束以及在混合目标团队中如何设计平台专属能力层。一、项目全景external_only_erlang 测试用例在 Gleam 仓库中test/目录下的每个子目录都是一个可独立编译、运行的集成测试项目。test/external_only_erlang 的名称已经说明了它的定位一个由于存在外部函数而只能在 Erlang 目标上运行的项目。它由四个文件组成文件作用src/external_only_erlang.gleamGleam 源码声明了仅 Erlang 实现的外部函数src/external_only_erlang_ffi.erlErlang 原生实现构成一个完整的 OTP 模块gleam.toml包配置声明编译目标与依赖test.sh驱动整个测试流程的 shell 脚本除此之外还有manifest.toml由 Gleam 生成的锁文件与README.md本文所依据的项目说明文档。这套结构本身就是 Gleam FFI 项目的最小可运行范本下面逐一深入。二、核心语法external属性与 Erlang 外部函数2.1 函数声明external_only_erlang.gleam的完整内容如下// This function is only implemented for Erlang, so if we try and call it from // JavaScript, or build this package for JavaScript, then the compiler will // (should) emit an error. external(erlang, external_only_erlang_ffi, main) pub fn main() - Nil关键点逐一拆解external属性这是 Gleam 声明外部函数external function的语法表示该函数没有 Gleam 函数体而是由目标平台的原生代码实现。三个参数依次为目标平台标识erlang、原生模块名external_only_erlang_ffi、原生函数名main。它告诉编译器在生成 Erlang 代码时调用main应直接映射为对 Erlang 模块external_only_erlang_ffi中main/0的调用。pub fn main() - Nil函数签名只声明了输入无参与输出Nil类型具体实现完全交给外部代码。注意外部函数必须带有类型标注——Gleam 编译器会在 compiler-core/src/error.rs 中明确提示 Functions with external implementations must have type annotations。注释中还给出了本项目要验证的预期行为如果尝试从 JavaScript 调用它或尝试把整个包构建为 JavaScript 目标编译器应当报错。2.2 Erlang 侧实现对应的原生实现external_only_erlang_ffi.erl是一个标准的 Erlang 模块-module(external_only_erlang_ffi). -export([main/0]). main() - io:format(Hello!\n, []).它导出了main/0调用时向标准输出打印一行Hello!。这个模块与 Gleam 源文件放在同一个src/目录下——Gleam 会把同目录下的.erl、.ex、.hrl、.mjs、.ts等文件识别为对应目标的 FFI 实现并在对应目标构建时一并编译进去。这正是 Gleam 常见的 FFI 组织方式.gleam声明类型与签名平台文件提供真实实现。从源码结构看Gleam 的 Erlang 后端会内联外部函数调用compiler-core/src/erlang/tests/external_fn.rs中的测试如Private external function calls are inlined、Public external function calls are inlined but the wrapper function is表明对于私有外部函数调用点直接替换为原生调用对于公开外部函数则保留一个薄封装wrapper后仍内联调用。这意味着外部函数在 Erlang 后端上几乎零开销。三、项目配置gleam.toml 中的目标声明与依赖name external_only_erlang version 1.0.0 target erlang [dependencies] hello_joe ~ 1.0 [dev_dependencies]这份配置与项目主题直接对应target erlang声明包的首选编译目标为 Erlang。它是与仅 Erlang 可用这个定位一致的显式标记表明该包面向 BEAM 生态设计。name external_only_erlang包名会进一步决定 Erlang 应用的命名otp_app 即external_only_erlang。hello_joe ~ 1.0依赖一个名为hello_joe的 Hex 包。在 test.sh 中这个依赖模块hello_joe被用来验证依赖模块可以无视目标自由运行这一关键行为见下节。manifest.toml中记录了锁定后的依赖解析结果gleam_stdlib 0.34.0与hello_joe 1.0.0均来自 Hex且hello_joe依赖gleam_stdlib其otp_app也写明了它是 Erlang 应用。值得对照的是镜像项目 test/external_only_javascript/gleam.toml除了名字不同它把target设为javascript外部函数声明变为external(javascript, ./external_only_javascript_ffi.mjs, main)原生实现是 external_only_javascript_ffi.mjs。两个项目一一对称恰好构成同一模式、两种目标的完整示例。四、行为验证test.sh 的完整测试流程项目的 test.sh 是该测试用例的剧本它通过gleam命令行默认用cargo run --quiet --从源码运行编译器也可用环境变量GLEAM_COMMAND覆盖为已安装的gleam验证了四个层次的编译行为4.1 阶段一依赖模块对所有目标都可用g run --modulehello_joe g run --modulehello_joe --targeterlang g run --modulehello_joe --targetjavascript脚本注释明确说明This should succeed regardless of target as it is a dependency module。依赖包中的纯 Gleam 模块可以面向任意目标运行——hello_joe不含平台专属代码因此无论默认目标、Erlang 还是 JavaScript 都能成功执行。这是 Gleam 跨目标兼容性的基础能否跑某个目标取决于代码本身是否使用了平台专属能力而不是依赖关系本身。4.2 阶段二Erlang 目标的构建与运行g build --targeterlang g run --targeterlang对当前项目根包指定--targeterlang构建并运行由于main的外部实现恰好是 Erlang 模块external_only_erlang_ffi:main/0这一阶段应当成功并输出Hello!。4.3 阶段三JavaScript 目标必须失败if g build --targetjavascript; then echo Expected build to fail exit 1 fi if g run --targetjavascript; then echo Expected run to fail exit 1 fi这是整个测试的核心断言为 JavaScript 目标构建、运行都必须失败。即使此前已经成功构建过 JavaScript 依赖阶段一根包的 JavaScript 构建仍会失败——因为main函数只有 Erlang 实现在 JavaScript 目标上它没有可用的外部实现。编译器在此会抛出NoImplementation类型的类型错误其提示文案位于 compiler-core/src/error.rs函数没有函数体也没有可用于该目标的外部实现Add a body or an external implementation using theexternalattribute。这里正是external单目标声明与多目标编译之间的核心矛盾点Gleam 类型系统在分析阶段就会根据当前编译目标检查每个外部函数是否有可用实现没有就拒绝编译把跨平台错误提前到构建期而非运行期暴露。4.4 阶段四Erlang shipment 导出g export erlang-shipment grep external_only_erlang_ffi build/erlang-shipment/external_only_erlang/ebin/external_only_erlang.app最后一步验证**发布物shipment**的完整性g export erlang-shipment会生成一个不依赖本机环境、可直接部署的 Erlang 发布包位于build/erlang-shipment/。随后用grep检查生成的应用描述文件external_only_erlang.app中确实列出了external_only_erlang_ffi模块——即 Erlang 外部实现被正确打包进了最终发布物可作为发布内容的验收手段。4.5 流程小结步骤命令预期结果清理构建目录rm -fr build保证从已知状态开始运行依赖模块g run --modulehello_joe [--targeterlang\|javascript]成功依赖对任意目标可用构建本包Erlangg build --targeterlang成功运行本包Erlangg run --targeterlang成功输出Hello!构建本包JavaScriptg build --targetjavascript失败NoImplementation 错误运行本包JavaScriptg run --targetjavascript失败导出发布物g export erlang-shipment grep.app成功且包含 FFI 模块五、编译错误与诊断NoImplementation 背后当面向 JavaScript 构建本项目时编译器会在类型检查阶段报错。查看 compiler-core/src/error.rs 中TypeError::NoImplementation分支约 L4450可以看到完整的用户提示结构错误说明该函数没有实现并给出两条修复路径——Add a body or an external implementation using theexternalattribute添加函数体或用external添加外部实现。从错误类型定义可以推断location字段会精确指向出问题的函数位置让开发者快速定位。这套错误机制同样服务于反向场景在external_only_javascript项目中src/external_only_javascript.gleam 的注释明确指出对 Erlang 构建会报同样的错。此外compiler-core/src/erlang/tests/external_fn.rs 中的快照测试如javascript_only、javascript_only_indirect、both_externals_no_valid_impl覆盖了大量外部函数边界情况包括某个目标缺失实现、间接引用缺失实现的外部函数、两个目标都没有有效实现等说明编译器对外部实现缺失的处理是经过系统测试的成熟路径。六、工程实践启示平台专属能力必须用external显式隔离需要调用 Erlang 的io:format、OTP 行为或 JavaScript 的 Web API 时一律通过external声明让编译器在目标构建时替你把关。用target声明包的预期平台像本项目的target erlang一样在gleam.toml中声明首选目标向工具链与协作方明确包的设计意图。依赖 ≠ 目标绑定纯 Gleam 的依赖模块对所有目标可用阶段一目标限制只来自代码中实际使用的平台实现。用集成测试固化跨目标行为本测试用例把Erlang 成功、JavaScript 失败、shipment 包含 FFI写成自动化断言防止未来重构悄悄破坏外部函数的目标约束——这是在多目标项目中维护 FFI 边界的可靠做法。镜像验证同时保留external_only_erlang与external_only_javascript两个对称用例对应 FFI 文件为.erl与.mjs可确保 FFI 机制在两个方向上都正确工作。对于需要同时服务 BEAM 与 JavaScript 的 Gleam 库本项目的启示是把平台专属逻辑压缩到极薄的external层让主体代码保持目标无关对于纯单平台的内部项目则可以直接像本项目一样锁定单一目标获得最简构建路径。【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价