Jai 入门 · 语法详解
Jai 入门 · 语法详解
Jai 是 Jonathan Blow(《Braid》《The Witness》作者)设计的面向性能与数据导向的静态强类型语言,目标之一是做“C 的现代替代品“。本文语料主要来自 GitHub 上的 JaiPrimer 与 The Way to Jai 社区教程。
1. Jai 的设计哲学
Jai 关注三件事:
- 写更快的代码:无 GC、无异常、无类继承包袱,程序员直接控制内存与性能特征;
- 减少摩擦:语法一致、重构成本低、编译快速;
- 数据导向(Data-Oriented Design):程序本质是“填充内存的数据 + 高效算法“。
| 与 C/C++ 对比 | C / C++ | Jai |
|---|---|---|
| 内存管理 | malloc/free 或 RAII | new / delete,指针所有权 ! 标记,无 GC |
| 多态 | 虚函数 / 模板 | 函数级多态 $T + 编译期代码 |
| 构建 | Make/CMake | 构建过程由 Jai 源码本身指定 |
| 数据组织 | AoS 为主 | struct SOA 一键切换 SoA/AoS |
| 反射 | 无原生支持 | 全类型信息运行时可查 |
2. 程序入口与基本结构
#import "Basic";
main :: () {
print("Hello, Jai!\n");
}要点:
main :: ()是入口过程;#import导入标准/自定义模块;print支持%占位符:print("x = %\n", x)。
3. 变量与类型
声明语法:名字 : 类型 = 值;
counter : int = 0;
name : string = "Jon";
average : float = 0.5 * (x + y);类型省略(编译器推断):名字 ::= 值;
counter := 0; // int
name := "Jon"; // string
average := 0.5 * (x+y); // float只声明不初始化
counter : int;
flag : bool;基本类型
bool、int、int8/16/32/64、uint、float、float32/64、string、rawptr 等。整数和浮点不隐式混用,需显式转换(cast)。
4. 函数(过程)
语法统一:局部函数、匿名函数、全局函数写法完全一致,这是 Jai 重构友好的核心设计。
sum :: (x: float, y: float, z: float) -> float {
return x + y + z;
}
main :: () {
print("Sum: %\n", sum(1, 2, 3));
}匿名函数:
add_one := (x: int) -> int { return x + 1; }:: 表示“绑定一个常量名字到右侧值“;过程、结构体、枚举、常量都用它声明。
5. 结构体 / 枚举 / 联合
struct
Vector3 :: struct {
x: float = 1;
y: float = 4;
z: float = 9;
}
v : Vector3;
v.x = 2;成员可以带默认值。没有构造函数/析构函数(Jai 刻意去掉 RAII)。
enum
Color :: enum u16 {
RED,
GREEN,
BLUE = 80,
ALPHA,
}union
Value :: union {
i: int;
f: float;
}6. 数组与切片
a : [50] int; // 定长数组
b : [..] int; // 动态数组
c : [] int; // 切片(宽指针,携带长度)数组不会退化为指针,自带长度信息 count:
print_int_array :: (a: [] int) {
n := a.count;
for i : 0..n-1 {
print("array[%] = %\n", i, a[i]);
}
}7. 控制流
if / else
if a == b {
print("equal\n");
} else if a > b {
print("greater\n");
} else {
print("less\n");
}if-case(对值列表匹配)
if x {
1, 2, 3 => print("small\n");
else => print("other\n");
}for
for i : 0..9 { // 范围
print("%\n", i);
}
for item : items { // 遍历集合
print("%\n", item);
}while
while count < 10 {
count += 1;
}defer(作用域退出时执行)
{
ptr := new int;
defer delete(ptr); // 离开作用域时自动释放
}8. 指针与内存
p : *int = new int; // 分配
p.* = 42; // 解引用(后缀 .*)
delete p; // 释放结构体指针所有权:成员类型前加 ! 表示“该指针由本结构体拥有,删除结构体时一并删除“:
node :: struct {
owned_a : !*node = null;
}
example := new node;
example.owned_a = new node;
delete example; // owned_a 也会被释放Jai 没有 GC 与自动内存管理,但用所有权标注 + defer 让手动管理可控。
9. 函数级多态($T)
Jai 的多态在函数级,参数前加 $ 声明类型变量:
sum :: (a: $T, b: T) -> T {
return a + b;
}
f3 := sum(1.0, 2.0); // float + float
i3 := sum(1, 2); // int + int类型由 $ 标记的参数推断,并可以做类型特化(#if 按类型分支)。
Any 类型
print_any :: (a: Any) {
if a.type.type == Type_Info_Tag.FLOAT {
print("a is float\n");
}
}Any 携带类型信息,适合需要“任意类型“参数的场景。
进阶 1:类型约束($T/Foo 与 $T/interface)
$T 不限制类型;要约束“必须拥有某组字段/成员“,用 / 写法(类似 trait / interface):
Vec3 :: struct {
x, y, z: float;
}
dot_product :: (a: $T/interface Vec3, b: T) -> float {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
// 任何"拥有 x/y/z float 字段"的结构体都能传入,不必继承 Vec3区别:
| 写法 | 含义 |
|---|---|
a: $T | 任意编译期已知类型 |
a: Foo($T) | 必须是 Foo 的某种特化,内层 $T 任意 |
a: $T/Foo | 只要结构体内 using 了 Foo 即可(组件式组合) |
a: $T/interface Foo | 只要拥有 Foo 的全部字段即可(结构鸭子类型) |
进阶 2:多态结构体(编译期参数化)
Foo :: struct(T: Type) {
some_data : T; // 运行时成员,随 T 变化
}
f : Foo(int);
print("%\n", f.T); // => int编译期参数不止类型,还可以是常量值,甚至函数:
Foo :: struct(
T: Type,
fun: (T) -> T, // 编译期烘焙,不占运行时内存
) {
value: T; // 运行时数据
}多态结构体的“模式匹配“写法——三种等价风格:
Hash_Table :: struct (K: Type, V: Type, N: int) {
keys: [N] K;
values: [N] V;
}
// 1) 显式模式匹配
f1 :: (t: Hash_Table($K, $V, $N), k: K, v: V) { }
// 2) $T/类型 + T.K 访问参数
f2 :: (t: $T/Hash_Table, k: T.K, v: T.V) { }
// 3) 隐式多态:直接用具体类型访问其编译期参数
f4 :: (t: Hash_Table, k: t.K, v: t.V) { }进阶 3:多态数组(长度与元素类型都是类型的一部分)
foo :: (x: [$N]$T) {
print("%: %, %\n", type_of(N), N, T);
}
x := int.[1, 2, 3, 4];
foo(x); // => s64: 4, s64(N 本身是编译期 int)注意:[4] int 与 [5] int 是不同类型,不能互相赋值。
进阶 4:由参数推导返回类型(Helper 结构体 + #run)
Helper :: struct(A: Type, B: Type) {
T :: #run helper(A, B); // 编译期计算返回类型
}
helper :: ($A: Type, $B: Type) -> Type {
T : Type = A; // 这里可以做类型层面的逻辑
return T;
}
combine :: (a: $A, b: $B) -> Helper(A, B).T {
// ...
}进阶 5:#modify——编译期校验/变换多态参数
do_something :: (T: Type) -> bool {
ti := cast(*Type_Info) T;
return ti.type == .INTEGER || ti.type == .ENUM || ti.type == .POINTER;
}
function :: (dest: *$T, value: T)
#modify { return do_something(T); } // false => 编译错误
{
// 运行时实现
}#modify 在每次调用解析时执行,可检查参数类型、做约束,是 Jai 在“类型层“做特化的入口。
10. 编译期执行(元编程核心)
#run:编译期运行任意代码
generate_table :: () -> [256] float {
table : [256] float;
for i : 0..255 {
table[i] = pow(i / 255.0, 2.2);
}
return table;
}
srgb_table := #run generate_table(); // 编译期生成,运行期直接查表编译期函数由编译器解释执行,结果直接烘焙进二进制。
#if:编译期条件
#if OS == "windows"
print("Windows build\n");
} else {
print("Other build\n");
}#if 也可用于结构体字段与枚举值的条件定义。
#bake:参数烘焙
add :: (a: int, b: int) -> int { return a + b; }
add_one := #bake add(1); // 等价于单参数函数11. 数据导向结构(SOA)
普通 AoS 与 struct SOA 只差一个关键字,内存布局自动切换:
Vector3 :: struct { // AoS:x y z x y z ...
x: float = 1;
y: float = 4;
z: float = 9;
}
Vector3SOA :: struct SOA { // SoA:x x x y y y z z z
x: float = 1;
y: float = 4;
z: float = 9;
}对缓存友好、批量更新场景(游戏实体、粒子系统)收益明显,且业务代码无需改动。
12. 反射与运行时类型信息
程序数据段保存完整类型表,可在运行时遍历:
for _type_table {
print("%\n", it.name);
}可用于序列化、网络复制、存档、调试器等(C/C++ 需要手写或宏实现)。
13. 模块与项目组织
#import "Basic";
#load "part2.jai"; // 追加编译另一个源文件大型项目常用 main.jai 作为结构总览:#import 模块、#load 分片源文件,再定义 main()。
14. 学习路径建议
- 语法骨架:变量 → 过程 → 结构体 → 控制流;
- 内存:指针、
new/delete、所有权!、defer; - 元编程:
#run、#if、$T多态、#bake; - 数据导向:
struct SOA、数组/切片、缓存局部性; - 实战:参考 The Way to Jai 与 JaiPrimer 的示例项目。
注意:Jai 仍处于开发期,语法细节随编译器迭代可能变化,以官方最新文档为准。