从 .vmodule 到「目录即模块」:UEFN 23.20 的模块体系变革
第 3 课说「建个文件夹就是建个模块」,这条规则不是从天上掉下来的——它是 UEFN 23.20 一次伤筋动骨的体系变革的产物。读懂这段历史,你会真正理解今天的模块默认为什么是 internal。
一、前世:靠 .vmodule 文件划地盘的年代
在 23.20 之前,Verse 项目里的模块要靠一种专门的 .vmodule 文件来定义:想要一个模块,就得放一个对应的 .vmodule 文件进去,模块的名字和边界由它说了算。文件夹只是文件夹,和模块没有必然关系。这套机制能用,但多一种文件就多一分心智负担——目录结构和模块结构是两张图,新人经常对不上号。
Epic 在官方公告《Verse module hierarchy changes in UEFN 23.20》里宣布了新的方案:模块改由目录名定义——项目 Verse 代码里的每个文件夹,自动成为一个同名模块;.vmodule 文件保留兼容,但正式进入弃用通道。从此目录树就是模块树,「建文件夹 = 建模块」,一张图看懂整个项目。这就是第 3 课里那句「每个文件夹自动成为同名模块」的由来。
二、关键语义差:默认 internal 与突然报错的引用
变革最容易被忽略、也最容易咬人的一点,是默认可见性变了:变革最容易被忽略、也最容易咬人的一点,是默认的「谁能用」变了——也就是蓝图里给变量、函数设 Public、Private 的那个访问权限,Verse 里管它叫可见性:旧的 .vmodule 模块默认是 public,谁都能引用;而新的目录模块默认是 <internal>——只在其所在模块内部可见。于是不少项目升级 23.20 后遭遇了灵异事件:代码一行没改,原本跑得好好的跨模块引用突然全线报访问权限错误。原因就是那些模块从「默认公开」悄悄变成了「默认内部」。
官方在公告里给出的补救办法,是用一个显式的模块声明把可见性钉死:在目录旁边的 Verse 文件里放一个 public 的 module 表达式,替这个目录模块声明可见性:
官方给出的补救办法,是手动补一句声明把「谁能用」钉死:在这个目录旁边放一个 .verse 文件,里面写一小段 module 声明并挂上 public 的牌子,替这个目录模块表态「我对外开放」:
# 与同名目录并列的 .verse 文件中:
# 用 module 表达式把模块显式声明为 public
MyModule<public> := module:
# 目录里的其余定义自动归入本模块
# TODO: 以 Verse API Reference 为准
公告同时列出了老项目的迁移步骤:把 Verse 文件从专用的 /Verse 文件夹移入 Content;删除与项目同名的 .vmodule 文件;再按模块名重命名目录。这也解释了第 3 课常见坑里那条「随手改文件夹名会弄断引用」——目录名承载了模块路径,动它就是动 API 地址。
三、为什么默认收紧:最小可见性原则
把默认值从 public 改成 internal,短期看是兼容性阵痛,长期看是刻意的设计:默认把门关上,想开放的地方由你显式声明。这就是软件工程里的「最小可见性原则」——一个成员暴露得越少,将来修改它就越自由;所有 public 都是一份长期承诺,承诺越少,演进越轻松。回想第 3 课引用的官方模块定义:「能在不破坏依赖方的情况下随时间演进」——默认 internal 正是为这句话服务的:没人依赖的东西,才谈得上随时重构。
所以今天你在 Verse 里跨模块引用时要同时给模块和成员标 <public>,不是语言在刁难你,而是它在逼你想清楚:这个东西,真的要对外承诺吗?
某项目升级到 UEFN 23.20 之后,代码一行没改,跨模块引用却突然报访问权限错误。根本原因是什么?
出处与延伸
本文整理自 Epic 开发者论坛官方公告: