Skip to content

模块系统规范

本文件定义 YaoXiang 编程语言的模块系统规范,包括模块定义、导入导出和作用域。


第一章:模块定义

1.1 模块基础

模块使用文件作为边界。每个 .yx 文件就是一个模块。

// 文件名即为模块名
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }

1.2 模块命名规则

  • 模块名由文件名决定
  • 文件扩展名 .yx 不参与模块名
  • 模块名使用 PascalCase 命名

第二章:模块导入

2.1 导入语法

Import       ::= 'use' ModuleRef ImportSpec?
ImportSpec   ::= ('{' ImportItems '}') ('as' AliasList)?
              |  'as' AliasList
ImportItems  ::= Identifier (',' Identifier)* ','?
AliasList    ::= Identifier (',' Identifier)*

2.2 导入方式

语法说明示例
use path;导入模块,使用最后部分访问use std.io; -> io.print
use path.{a, b};导入指定项use std.io.{print}; -> print
use path as alias;导入并重命名use std.io as io; -> io.print
use path.{i1, i2} as a, b;导入指定项并重命名use std.io.{print, read} as p, r; -> p, r

2.3 导入示例

yaoxiang
// 导入整个模块
use std.io
io.print("Hello")

// 导入指定项
use std.io.{print, read}
print("Hello")

// 导入并重命名
use std.io as io_module
io_module.print("Hello")

// 导入指定项并重命名
use std.io.{print, read} as p, r
p("Hello")

第三章:模块导出

3.1 pub 关键字

使用 pub 关键字声明导出项:

yaoxiang
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }

// 私有项(不导出)
internal_value: Int = 42

3.2 导出规则

  • 默认所有项都是私有的
  • 使用 pub 声明的项可以被其他模块访问
  • 私有项只能在当前模块内访问

3.3 pub 自动绑定

使用 pub 声明的函数,编译器自动绑定到同文件定义的类型:

yaoxiang
// 使用 pub 声明,编译器自动绑定
pub distance: (p1: Point, p2: Point) -> Float = {
    dx = p1.x - p2.x
    dy = p1.y - p2.y
    (dx * dx + dy * dy).sqrt()
}

// 编译器自动推断:
// 1. Point 在当前文件定义
// 2. 函数参数包含 Point
// 3. 执行 Point.distance = distance[0]

// 调用
d = distance(p1, p2)           // 函数式
d2 = p1.distance(p2)           // OOP 语法糖

第四章:作用域

4.1 模块作用域

每个模块都有自己的作用域,模块内的项默认对外不可见。

4.2 嵌套作用域

yaoxiang
// 块作用域
{
    x = 10
    // x 在此作用域内可见
}
// x 在此作用域外不可见

// 函数作用域
add: (a: Int, b: Int) -> Int = {
    result = a + b
    return result
}
// result 在函数外不可见

4.3 变量声明与遮蔽

YaoXiang 没有 let 关键字。x = value 是声明还是赋值?遵循一个原则:

赋值优先。 声明只有一次,赋值却有百次。让高频操作走最短路径。

x = value:
  沿作用域链向外查找 x
    → 找到 mut x          :赋值,OK(通过 &mut 令牌)
    → 找到 x(已 moved)   :视为"未找到有效绑定",在当前作用域重新声明
    → 找到 x(不可变,存活):E2010 不可重新赋值
    → 找不到              :在当前作用域新声明(唯一的声明路径)

mut x = value:
    → 当前作用域已存在 x :E2002 重复定义
    → 外层作用域存在 x   :E2013 禁止遮蔽(显式新声明不能与外层同名)
    → 无冲突              :新可变声明
  • 同作用域:任何名字只能声明一次(E2002)
  • 内层无 mut:优先查找外层,赋值或报错
  • 内层有 mut:显式新声明,禁止与外层同名(E2013)

同作用域

yaoxiang
x = 10
x = 20              // E2002:'x' 已在此作用域定义

mut y = 10
y = 20              // OK:同一绑定,重新赋值
mut y = 30          // E2002:'y' 已在此作用域定义

z = 10
mut z = 20          // E2002:'z' 已在此作用域定义(mut 不能覆盖已有声明)

Move 后重新绑定

不可变变量如果拥有所有权,当它的值被 move(消耗)后,原绑定进入 moved 状态——名字仍然占据作用域槽位,但值已不可访问。此时 x = value 不是修改旧绑定,而是在同一作用域内重新声明 x

赋值优先查找的"已 moved"分支:
  x 在当前作用域存在,但处于 moved 状态
    → 编译器视为"未找到有效绑定"
    → 在当前作用域重新声明 x(覆盖旧的 moved 槽位)

核心机制: 旧值被消耗后,绑定失效,名字回归"可声明"状态。这不是遮蔽——旧绑定已经不存在了。

yaoxiang
// 管道式数据流:每一步消耗旧值,产生新值
data = fetch()           // 不可变,持有所有权
data = transform(data)   // move data → 旧 data 失效,新 data 重新绑定
data = filter(data)      // 同上
process(data)

// 等价的显式写法(对比):
data1 = fetch()
data2 = transform(data1)  // data1 被 move,不可再用
data3 = filter(data2)     // data2 被 move,不可再用
process(data3)

语义分离:

操作含义机制语法
重新绑定旧值消失,新值诞生move + 重新声明x = f(x)
原地修改同一内存位置的值变化mut 赋值mut x; x = v

为什么这不同于遮蔽:

  • 遮蔽(Rust 的 let x = ...):旧绑定仍然存在,只是被新绑定遮住了
  • Move 后重新绑定:旧绑定已被消耗,名字回归未初始化状态,重新声明是唯一出路

约束:

  • 只有拥有所有权的值才能被 move。引用(&T&mut T)被复制而非移动
  • Move 检查是编译期完成的,moved 状态的变量在任何表达式中被读取都会报 E2014
  • IDE 可以在 moved 变量上显示灰色提示,表明该名字处于未初始化状态
yaoxiang
// move 后读取 → 错误
data = fetch()
result = process(data)   // data 被 move
print(data)              // E2014:'data' 已被 move,不可再使用

// 引用不会触发 move
ref_data = &value
copy1 = ref_data         // 复制引用,ref_data 仍可用
copy2 = ref_data         // OK

// 跨作用域:moved 状态穿透
data = fetch()
{
    data = transform(data)  // move 外层 data → 重新绑定(内层新声明)
    print(data)             // OK:使用内层 data
}
print(data)                 // E2014:外层 data 已被 move

跨作用域

yaoxiang
// 外层不可变,内层赋值 → 不可变变量不能重新赋值
x = 10
{
    x = 20          // E2010:'x' 不可变,不能重新赋值
}
{
    mut x = 20      // E2013:不能遮蔽已有变量 'x'(显式声明新绑定)
}

// 外层 mut,内层赋值 → 修改同一绑定
mut y = 10
{
    y = 20          // OK:同一绑定,通过 &mut 令牌修改
}
print(y)            // 20

// 外层 mut,内层不能声明同名
mut z = 10
{
    z = 30          // OK:同一绑定
}
{
    mut z = 30      // E2013:不能遮蔽已有变量 'z'
}

// 多层嵌套:mut 穿透所有层级
mut a = 0
{
    {
        a = 10      // OK
    }
}
print(a)            // 10

// 不可变穿透所有层级也不能重新赋值
b = 0
{
    {
        b = 10      // E2010:'b' 不可变,不能重新赋值
    }
}

for 循环

yaoxiang
// 循环变量每次迭代是新绑定,不是修改
for i in 1..5 {
    print(i)        // OK:每次迭代绑定新值
    i = 10          // E2010:不可变循环变量,不能重新赋值
}

for mut i in 1..5 {
    i = 10          // OK:可变循环变量
}

// 循环变量不能遮蔽外层
i = 0
for i in 1..5 {     // E2013:不能遮蔽已有变量 'i'
}

// mut 外层累加器在循环体内可修改
mut sum = 0
for i in 1..5 {
    sum = sum + i   // OK:同一绑定,通过 &mut 令牌修改
}
print(sum)          // 15

// 不可变外层在循环体内不能修改
sum2 = 0
for i in 1..5 {
    sum2 = sum2 + i // E2010:'sum2' 不可变,不能重新赋值
}

相关错误码

错误码消息触发场景
E2002'{name}' is already defined in this scope同作用域重复声明(无论 mut 与否)
E2010Cannot assign to immutable variable '{name}'内层无 mut 赋值时,外层变量不可变且未 moved
E2013Cannot shadow existing variable '{name}'内层显式声明(mut xx: Type)与外层同名
E2014'{name}' has been moved and cannot be used读取已 moved 的变量

第五章:模块组织

5.1 目录结构

src/
├── main.yx          // 主模块
├── math/
│   ├── index.yx     // 数学模块入口
│   ├── vector.yx    // 向量模块
│   └── matrix.yx    // 矩阵模块
└── utils/
    ├── index.yx     // 工具模块入口
    └── string.yx    // 字符串工具

5.2 模块入口

目录中的 index.yx 文件作为模块入口:

yaoxiang
// math/index.yx
use math.vector
use math.matrix

pub Vector = vector.Vector
pub Matrix = matrix.Matrix

5.3 相对导入

yaoxiang
// 在 math/vector.yx 中
use math.matrix  // 绝对导入
use .matrix      // 相对导入(同目录)

附录:模块语法速查

A.1 模块即文件

// 文件名.yx 即为模块名
Import ::= 'use' ModuleRef

A.2 导入导出

yaoxiang
// 导入
use std.io
use std.io.{print, read}
use std.io as io

// 导出
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }