news 2026/9/13 6:08:10

Gleam 外部函数单目标实现的编译限制解析:external_only_javascript 测试项目实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Gleam 外部函数单目标实现的编译限制解析:external_only_javascript 测试项目实战

Gleam 外部函数单目标实现的编译限制解析:external_only_javascript 测试项目实战

【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam

在 Gleam 编译器的多目标(Erlang 与 JavaScript)构建体系中,通过@external注解绑定的外部函数(External Function)可以只针对单一目标提供实现。本文以仓库中的 external_only_javascript 测试项目为主线,从配置、源码、编译错误到验证脚本,完整剖析"外部函数仅实现于 JavaScript 时,Erlang 目标构建如何失败、JavaScript 目标又如何成功"的完整机制,帮助读者理解 Gleam FFI(外部函数接口)的跨目标约束与编译器校验逻辑。

项目定位:一个"只能跑在 JavaScript 上"的演示工程

external_only_javascript是 Gleam 仓库test/目录下的一组集成测试工程之一。它的 README 用一句话点明了项目本质:

A project that can only be run on JavaScript due to an external function.

即:由于一个外部函数只有 JavaScript 实现,整个项目只能在 JavaScript 目标上运行。与它镜像对应的是 external_only_erlang——一个因外部函数仅实现于 Erlang 而只能运行在 Erlang 上的工程。二者共同验证了 Gleam 编译器对"外部函数缺失目标实现"场景的报错行为。

项目文件结构如下:

test/external_only_javascript/ ├── src/ │ ├── external_only_javascript.gleam # 使用 @external(javascript, ...) 注解的入口模块 │ └── external_only_javascript_ffi.mjs # JavaScript 侧的外部函数实现 ├── gleam.toml # 项目配置(target = "javascript") ├── manifest.toml # 依赖锁定清单 └── test.sh # 自动化验证脚本

配置文件解读:target 与依赖

gleam.toml 中的关键配置如下:

name = "external_only_javascript" version = "1.0.0" target = "javascript" [dependencies] hello_joe = "~> 1.0" [dev_dependencies]
  • target = "javascript":声明项目的默认构建目标为 JavaScript。Gleam 支持在配置中指定erlangjavascript作为默认目标,命令行可通过--target覆盖;
  • 依赖hello_joe(版本约束~> 1.0):一个纯 Gleam 编写的依赖模块,用于验证"依赖模块在两种目标下都能正常构建运行"这一前提(详见 manifest.toml,其锁定了gleam_stdlib 0.34.0hello_joe 1.0.0)。

从配置层面看,这个项目本身并没有声明任何特殊机制;真正的"陷阱"藏在入口模块的@external注解中。

核心源码:只绑定 JavaScript 实现的外部函数

入口模块 external_only_javascript.gleam 的完整内容如下:

// This function is only implemented for JavaScript, so if we try and call it // from Erlang, or build this package for Erlang, then the compiler will // (should) emit an error. @external(javascript, "./external_only_javascript_ffi.mjs", "main") pub fn main() -> Nil

逐项拆解这个@external注解:

参数含义
目标平台javascript指明该外部实现只适用于 JavaScript 目标
实现路径"./external_only_javascript_ffi.mjs"相对于当前 Gleam 模块的 FFI 文件路径
导出函数名"main"在 FFI 文件中调用的导出函数

对应的 FFI 实现 external_only_javascript_ffi.mjs 极为简单:

export function main() { console.log("Hello"); }

注意两个细节:

  1. 没有 Erlang 实现:模块中不存在@external(erlang, ...)注解,也不存在.erl文件,因此该函数在 Erlang 目标下没有任何可用实现;
  2. 注解与模块名同名函数的组合main函数完全由外部实现承担,Gleam 模块内没有函数体(body),这正是"外部函数替换 Gleam 实现"的标准用法——当存在外部实现时,Gleam 允许函数体为空,但要求显式标注类型注解。

编译器侧校验:目标实现缺失如何被拦截

当为 Erlang 目标构建该包时,编译器会在类型检查(type checking)阶段发现"当前目标没有实现",并报错。这一逻辑位于 compiler-core/src/analyse.rs#L566-L576:

// Find the external implementation for the current target, if one has been given. let external = target_function_implementation(target, &external_erlang, &external_javascript); // The function must have at least one implementation somewhere. let has_implementation = self.ensure_function_has_an_implementation( &body, &external_erlang, &external_javascript, location, );

其中target_function_implementation负责按当前构建目标挑选对应的外部实现(Erlang 实现或 JavaScript 实现),而ensure_function_has_an_implementation则校验函数是否"至少在某处存在实现"。当函数体为空、且当前目标没有任何外部实现时,就会抛出UnsupportedExpressionTarget类型的错误。

对应的错误文案定义在 compiler-core/src/error.rs#L4602-L4630:

This value is not available as it is defined using externals, and there is no implementation for the {target} target. Did you mean to build for a different target?

即标题为"Unsupported target"的编译错误,提示"该值由外部函数定义,但当前目标没有实现",并建议改用其他目标构建。

此外,对于公开(pub)函数,编译器还有更严格的约束:所有包内公开函数都必须能针对当前目标编译。对应错误为UnsupportedPublicFunctionTarget,文案见 compiler-core/src/error.rs#L4632-L4645:

The `{name}` function is public but doesn't have an implementation for the {target} target. All public functions of a package must be able to compile for a module to be valid.

这意味着"只实现单一目标的外部函数"只能安全地用于**私有(非 pub)**函数,或仅在单一目标下发布/使用的包中。

验证脚本:test.sh 的完整行为矩阵

test.sh 是一份可直接运行的 shell 验证脚本(通过GLEAM_COMMAND环境变量可指定 Gleam 命令,默认为cargo run --quiet --,即从源码运行编译器)。它逐条断言了以下行为:

第一步:依赖模块在任何目标下都可用

g run --module=hello_joe g run --module=hello_joe --target=erlang g run --module=hello_joe --target=javascript

hello_joe是纯 Gleam 依赖,没有外部函数,因此在默认目标、Erlang 目标和 JavaScript 目标下都能成功运行。

第二步:JavaScript 目标的构建与运行应当成功

g build --target=javascript g run --target=javascript

由于main的外部实现存在于external_only_javascript_ffi.mjs,JavaScript 目标可以完成构建,并输出Hello

第三步:Erlang 目标的构建与运行应当失败

if g build --target=erlang; then echo "Expected build to fail" exit 1 fi if g run --target=erlang; then echo "Expected run to fail" exit 1 fi

脚本特意强调"even if previously an Erlang dependency was built"(即使之前已经构建过 Erlang 依赖)——也就是说,编译缓存中已存在 Erlang 目标产物,也不会让主模块逃过校验:main没有 Erlang 实现,Erlang 构建必然失败,脚本据此反向断言(if ... then exit 1)验证编译器确实报错。

对照实验:external_only_erlang 的镜像场景

为了确认该机制与目标方向无关,仓库提供了镜像工程 external_only_erlang。其入口模块 external_only_erlang.gleam 使用:

@external(erlang, "external_only_erlang_ffi", "main") pub fn main() -> Nil

对应验证脚本 test.sh 的行为与 JavaScript 版本完全对称:

  • g run --module=hello_joe --target=erlang--target=javascript均成功(依赖无限制);
  • g build --target=erlangg run --target=erlang成功(Erlang 有实现);
  • g build --target=javascriptg run --target=javascript失败(JavaScript 无实现,即使此前已构建过 JavaScript 依赖)。

这组对照工程共同证明:"外部函数缺失某目标实现"的编译错误由编译器统一保证,不依赖构建缓存、不区分目标方向,且错误在构建(build)与运行(run)阶段都会触发。

实践要点与小结

通过这个测试项目,可以提炼出使用 Gleam 外部函数时的关键实践结论:

  1. @external(target, module, fn)是实现 FFI 的标准入口:目标参数只填erlangjavascript之一,即可声明单目标实现;两个目标各写一个@external注解(配合@target.erlang/@target.javascript条件编译)则实现双目标 FFI;
  2. 单目标实现意味着单目标可用:若一个包对外暴露了仅单目标实现的外部函数,则另一个目标将无法构建该包——这正是external_only_javascript项目名字的由来;
  3. 编译器在类型检查阶段拦截:相关逻辑位于 compiler-core/src/analyse.rs 的target_function_implementationensure_function_has_an_implementation,错误文案见 compiler-core/src/error.rs 的UnsupportedExpressionTargetUnsupportedPublicFunctionTarget,并附有"Did you mean to build for a different target?"的提示;
  4. 依赖模块不受影响:纯 Gleam 依赖(如测试中的hello_joe)在两种目标下均可正常构建运行,限制只作用于自身使用了单目标外部函数的模块;
  5. 构建缓存不掩盖错误:即使此前已成功构建过另一目标的依赖,主模块的目标缺失错误依然会触发,保证跨目标构建的确定性。

对希望为单一平台(如纯 JavaScript 的 Deno/Node 项目,或纯 Erlang/OTP 项目)编写 Gleam 包的开发者而言,external_only_javascriptexternal_only_erlang这两个测试工程就是最精简的可复现模板:一份 Gleam 入口、一份 FFI 文件、一份target配置,外加test.sh中基于--target的成功/失败断言,即可完整验证你的 FFI 跨目标约束是否符合预期。

【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 6:06:57

用 3 个任务上手 FreeCAD:免费开源 3D 参数化建模完整指南

用 3 个任务上手 FreeCAD:免费开源 3D 参数化建模完整指南 【免费下载链接】FreeCAD Official source code of FreeCAD, a free and opensource multiplatform 3D parametric modeler. 项目地址: https://gitcode.com/GitHub_Trending/fr/FreeCAD FreeCAD 是…

作者头像 李华
网站建设 2026/9/13 6:00:43

C#/VB上位机与三菱FX5U通讯:MC协议配置、寄存器读写与调试实战

简介:C#与三菱FX5U PLC的TCP通讯交互源码包,面向工控软件开发人员、PLC与PC通信初学者及有项目集成经验的工程师。资源同时提供VB.NET与C#两个版本工程,支持整数、双整数、浮点数等多种数据类型,并兼容ASCII和二进制两种报文格式&…

作者头像 李华
网站建设 2026/9/13 6:00:03

Loqua 使用指南:让思维不再因键盘而减速

Loqua 使用指南:让思维不再因键盘而减速 传统键盘 45 wpm,Loqua 语音输入 220 wpm。把粗略的想法变成即用型文字、理解屏幕上的内容、选择聆听而非阅读、用语音推进改写/翻译/日程/编程——减少打字,减少上下文切换,更多时间沉浸于…

作者头像 李华
网站建设 2026/9/13 5:58:32

Qwen 3.5 Plus大模型本地化部署与显存优化实战

1. Qwen 3.5 Plus部署方案概述Qwen 3.5 Plus作为阿里云推出的旗舰级大语言模型,其强大的多模态能力和智能体特性使其在编程、办公自动化等场景表现优异。但传统部署方式对显存的高需求(通常需要40GB以上显存)将大多数个人开发者拒之门外。最新…

作者头像 李华
网站建设 2026/9/13 5:57:01

多晶振变单时钟芯片:硬件时序架构的范式迁移

1. 为什么工程师开始把板子上七八颗晶振“砍”成一颗芯片? 我第一次在客户产线看到那块老主板时,差点以为自己眼花了:密密麻麻贴了9颗晶振——25MHz给CPU,12MHz给USB PHY,32.768kHz给RTC,还有4颗不同频点的…

作者头像 李华