Skip to Main Content
Jai 入门 · 语法详解Back to Top

Jai 入门 · 语法详解

7 minutes

Jai 入门 · 语法详解

Jai 是 Jonathan Blow(《Braid》《The Witness》作者)设计的面向性能与数据导向的静态强类型语言,目标之一是做“C 的现代替代品“。本文语料主要来自 GitHub 上的 JaiPrimer 与 The Way to Jai 社区教程。


1. Jai 的设计哲学

Jai 关注三件事:

  1. 写更快的代码:无 GC、无异常、无类继承包袱,程序员直接控制内存与性能特征;
  2. 减少摩擦:语法一致、重构成本低、编译快速;
  3. 数据导向(Data-Oriented Design):程序本质是“填充内存的数据 + 高效算法“。
与 C/C++ 对比C / C++Jai
内存管理malloc/free 或 RAIInew / delete,指针所有权 ! 标记,无 GC
多态虚函数 / 模板函数级多态 $T + 编译期代码
构建Make/CMake构建过程由 Jai 源码本身指定
数据组织AoS 为主struct SOA 一键切换 SoA/AoS
反射无原生支持全类型信息运行时可查

2. 程序入口与基本结构

#import "Basic";

main :: () {
    print("Hello, Jai!\n");
}

要点:


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;

基本类型

boolintint8/16/32/64uintfloatfloat32/64stringrawptr 等。整数和浮点不隐式混用,需显式转换(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. 学习路径建议

  1. 语法骨架:变量 → 过程 → 结构体 → 控制流;
  2. 内存:指针、new/delete、所有权 !defer
  3. 元编程#run#if$T 多态、#bake
  4. 数据导向struct SOA、数组/切片、缓存局部性;
  5. 实战:参考 The Way to JaiJaiPrimer 的示例项目。

注意:Jai 仍处于开发期,语法细节随编译器迭代可能变化,以官方最新文档为准。