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

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 支持在配置中指定erlang或javascript作为默认目标命令行可通过--target覆盖依赖hello_joe版本约束~ 1.0一个纯 Gleam 编写的依赖模块用于验证依赖模块在两种目标下都能正常构建运行这一前提详见 manifest.toml其锁定了gleam_stdlib 0.34.0与hello_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); }注意两个细节没有 Erlang 实现模块中不存在external(erlang, ...)注解也不存在.erl文件因此该函数在 Erlang 目标下没有任何可用实现注解与模块名同名函数的组合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-L4630This 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-L4645The {name} function is public but doesnt 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 --modulehello_joe g run --modulehello_joe --targeterlang g run --modulehello_joe --targetjavascripthello_joe是纯 Gleam 依赖没有外部函数因此在默认目标、Erlang 目标和 JavaScript 目标下都能成功运行。第二步JavaScript 目标的构建与运行应当成功g build --targetjavascript g run --targetjavascript由于main的外部实现存在于external_only_javascript_ffi.mjsJavaScript 目标可以完成构建并输出Hello。第三步Erlang 目标的构建与运行应当失败if g build --targeterlang; then echo Expected build to fail exit 1 fi if g run --targeterlang; 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 --modulehello_joe --targeterlang与--targetjavascript均成功依赖无限制g build --targeterlang与g run --targeterlang成功Erlang 有实现g build --targetjavascript与g run --targetjavascript失败JavaScript 无实现即使此前已构建过 JavaScript 依赖。这组对照工程共同证明外部函数缺失某目标实现的编译错误由编译器统一保证不依赖构建缓存、不区分目标方向且错误在构建build与运行run阶段都会触发。实践要点与小结通过这个测试项目可以提炼出使用 Gleam 外部函数时的关键实践结论external(target, module, fn)是实现 FFI 的标准入口目标参数只填erlang或javascript之一即可声明单目标实现两个目标各写一个external注解配合target.erlang/target.javascript条件编译则实现双目标 FFI单目标实现意味着单目标可用若一个包对外暴露了仅单目标实现的外部函数则另一个目标将无法构建该包——这正是external_only_javascript项目名字的由来编译器在类型检查阶段拦截相关逻辑位于 compiler-core/src/analyse.rs 的target_function_implementation与ensure_function_has_an_implementation错误文案见 compiler-core/src/error.rs 的UnsupportedExpressionTarget与UnsupportedPublicFunctionTarget并附有Did you mean to build for a different target?的提示依赖模块不受影响纯 Gleam 依赖如测试中的hello_joe在两种目标下均可正常构建运行限制只作用于自身使用了单目标外部函数的模块构建缓存不掩盖错误即使此前已成功构建过另一目标的依赖主模块的目标缺失错误依然会触发保证跨目标构建的确定性。对希望为单一平台如纯 JavaScript 的 Deno/Node 项目或纯 Erlang/OTP 项目编写 Gleam 包的开发者而言external_only_javascript与external_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),仅供参考