Koral 编程语言

June 27, 2026 · View on GitHub

Koral 是一个专注于性能、可读性和实用跨平台开发的开源编程语言。

通过精心设计的语法规则,这门语言可以有效降低读写负担,让你能够把真正的注意力放在解决问题上。

规范说明:

  • 本文档是面向使用者的语言参考。
  • 对语法敏感的问题,以 docs/grammar.bnf 和当前编译器行为为准。
  • 如果本文档示例与编译器行为不一致,应以编译器行为为真值,并同步修正文档。

关键特性

  • 现代化、易于辨识的语法,支持可选分号和表达式导向控制流:ifwhen 与块表达式可以产值,whilefor 仍然只作为语句使用。
  • 基于引用计数、所有权分析和逃逸分析的自动内存管理。
  • 带有 Trait 约束的泛型系统,通过单态化实现零成本抽象。
  • 代数数据类型(结构体与枚举)配合穷尽式模式匹配。
  • 基于 Trait 的多态,支持 Trait 对象实现运行时分发。
  • 一等函数、Lambda 表达式和闭包。
  • 多范式编程(函数式与命令式结合)。
  • 模块系统,支持访问控制(public / protected public / protected / private)。
  • 外部函数接口(FFI),与 C 语言无缝互操作。
  • C 后端,广泛的平台兼容性。

安装与使用

目前 Koral 编译器会先生成 C 代码并调用 clang 完成后端编译,因此请确保 PATH 中可找到 clang

编译与运行

你既可以直接编译单个源码文件,也可以编译 koral.json 中声明的模块图。

  1. 直接构建单文件
    koralc build hello.koral
    
  2. 构建 manifest 中声明的目标模块
    koralc build --package-config koral.json --target-module app::main
    
  3. 仅类型检查
    koralc check --package-config koral.json --target-module app::main
    
  4. 编译并运行:使用 run 命令一步完成编译与执行。
    koralc run --package-config koral.json --target-module app::main
    
  5. 仅生成 C:使用 emit-c 只输出 C 源码。
    koralc emit-c --package-config koral.json --target-module app::main -o out
    

常用选项:

  • -o, --output <dir>:输出目录
  • --package-config <path>:从包 manifest 构建
  • --target-module <name>:选择 manifest 中的目标模块
  • --deps-root <path>:manifest 构建模式下的依赖根目录
  • --std-config <path>:显式指定 std manifest
  • --no-std:编译时不加载 std/koral.json 中声明的模块

基础语法

基本语句与分号

在 Koral 内,语句是最小的组成单位。

分号插入遵循以下规则:

  • 语句可以显式使用分号 ; 结束。
  • 当 parser 不处于续行上下文时,换行可以结束当前语句。
  • 位于 ()[]{} 内部的换行不会结束外围语句。
  • 空行和注释会打断续行。
let a = 0;
let b = 1;

// 结尾是换行,省略分号
let c = { 
    1 + 1 
} 

// 结尾是换行,省略分号
let d = 1
let e = 2

入口函数

每个可执行程序都需要一个入口点。在 Koral 中,这个入口点是 main 函数。一个典型的 main 函数声明如下。

let main() Void = {}

这里我们声明了一个名称为 main 的函数。= 右边是函数体,{} 表示一个空的块表达式,返回 Void

main 函数也可以接受参数(命令行参数)并返回整数(状态码),但这取决于具体的运行环境支持。关于函数的更多细节将在之后的章节中说明。

显示信息

现在让我们的程序输出一些内容看看,标准库提供了 println 函数,用于向标准输出打印一行文本。

let main() Void = println("Hello, world!")

现在尝试执行这个程序,我们可以看到控制台上显示了 Hello, world!

注释

注释是代码中被编译器忽略的部分,用于向阅读代码的人提供解释。

例如:

// 这是一个单行注释,从双斜杠开始直到行尾

/*
    这是一个块注释。
    它可以跨越多行。
    /* Koral 支持嵌套的块注释 */
*/

变量

Koral 的变量是一种绑定语义,相当于是把一个变量名和一个值绑定在一起,从而建立起了关联关系,类似于键值对。为了安全性的考虑,变量默认是不可以改变的,当然我们也提供了另一种变量——可变变量。

只读变量

在 Koral 中是通过 let 关键字来声明只读变量的,变量遵循先声明后使用的原则。

Koral 通过静态类型确保类型安全。变量绑定可以在声明时显式标注类型。在上下文中有足够的信息时,我们也可以省略类型,编译器会从上下文中推断出变量的类型。

示例代码如下:

let a Int = 5   // 显式标注类型
let b = 123     // 自动推断类型

一旦只读变量被声明之后,它的值在当前作用域内就不会再被改变。

如果我们尝试对只读变量赋值,编译器会报错。

let a = 5
a = 6 // 错误

可变变量

如果我们需要一个可以被重新赋值的变量,可以使用可变变量声明。

在 Koral 中通过 let mut 关键字来声明可变变量,同样遵循先声明后使用的原则。

示例代码如下:

let mut a Int = 5   // 显式标注类型
let mut b = 123     // 自动推断类型

Pair 解构绑定

当右侧表达式的类型为 Pair 时,可以使用括号语法将两个元素分别绑定到独立的变量。每个绑定位置支持 _(丢弃)、mut(可变)以及可选的类型标注。

let (a, b) = (1, 2)                  // 类型推断
let (c Int, d String) = (3, "hello")  // 显式类型标注
let (mut e, f) = (10, 20)             // 可变绑定
let (_, g) = (1, 2)                   // 丢弃第一个元素

编译器会直接从 Pair 值中移动字段到目标变量,避免不必要的拷贝和析构开销。

引用创建规则(.ref / box

Koral 区分托管引用和借用引用两种引用类型:

  • ref T / ref mut T 是托管引用。
    • 允许逃逸,可以返回、存字段、存容器、存枚举 payload。
  • ref '_ T / ref '_ mut T 是借用引用(匿名生命周期)。
    • 不允许逃逸,不允许返回、存字段、存枚举 payload、全局或闭包环境。
    • 仅允许出现在函数参数和 receiver 位置。
  • 两种引用在运行时布局相同,retain / release 行为也相同;区别只在前端静态语义。
  • ref mut T 可隐式转换为 ref Tref '_ mut T 可隐式转换为 ref '_ T。反向不允许。

.ref 采用"借用优先"的语义:

  • 只允许从值路径构造,例如 x.refself.field.ref
  • 不允许对右值直接写 .ref(例如 make_value().ref 会报错)。
  • 没有显式托管期望类型时,.ref 默认构造借用引用。
  • 如果当前局部上下文已经明确要求托管 ref / ref mut,编译器会在当前函数内把这次 .ref 局部提升成托管引用。
  • box(expr) 是显式托管构造,返回 ref mut T

隐式转换规则:

  • 函数参数和方法参数均不允许隐式 ref 提升或 auto-deref。 如果函数期望 ref T,调用方必须用 a.ref 显式传入。如果函数期望 T,传入 ref T 需要显式用 .val 解引用。此规则适用于所有参数,包括方法参数。
  • auto-ref 和 auto-deref 仅对 method receiver(self)生效。 self ref 方法可接受值(auto-ref);self 方法可接受 ref T(auto-deref,跟随 Go 的 pointer receiver 设计)。

接收者调整规则:

  • self ref 是借用 receiver,可接受左值和右值接收者;对右值接收者,编译器会物化稳定的临时值。
  • self ref mut 是借用可变 receiver,仍然要求可写左值接收者;右值会被拒绝。
  • self ref 在右值上可能引入临时值物化,此类调用可能带来隐藏的 retain/分配开销。
let mut x = 10
let rx ref mut Int = x.ref   // 托管期望类型触发局部提升

let y = 10
let ry ref '_ Int = y.ref    // 默认得到借用引用

let owned ref mut Int = box(42) // box() 返回 ref mut T

// let rz = 42.ref           // 错误:右值不能借用

// 函数参数不允许隐式 ref 提升:
let takes_ref(r ref Int) Int = r.val
let v = 42
// takes_ref(v)              // 错误:期望 ref Int,得到 Int
takes_ref(v.ref)             // OK:显式 .ref

// auto-deref 仅对 receiver 生效:
type Counter(mut value Int)
given Counter {
    public get(self ref) Int = self.value
}
let c = Counter(10)
c.get()                      // OK:self ref 接受值(auto-ref)

赋值

对于可变变量,我们可以在需要的时候多次改变它的值。

Koral 的赋值语句与大多数语言一样,都使用 = 声明,= 左边必须是可以被赋值的变量,程序会将 = 右边的值赋值给左边的变量。

示例代码如下:

let mut a = 0
a = 1  // 合法
a = 2  // 合法

块表达式

在 Koral 中,{} 表示一个块表达式。

块表达式规则:

  • 块中可以包含零条或多条语句。
  • 普通块表达式的默认类型是 Void
  • returnbreakcontinue 可以让块提前结束,因此对应块类型会变成 Never
  • yield 不是块返回机制;它只能出现在 if / when 表达式的分支 body 内。
  • returnbreakcontinue 结尾的块类型为 Never

通过块表达式可以组合一系列语句。

let a Void = {}
let main() Void = {
    let c = 7
    let d = c + 14
    println(((c + 3) * 5 + d / 3).to_string())
}

标识符

标识符就是给变量、函数、类型等指定的名字。构成标识符的字母均有一定的规范,这门语言中标识符的命名规则如下:

  1. 区分大小写。Myname 与 myname 是两个不同的标识符。
  2. 类型(Type)和构造器(Constructor)必须以大写字母开头(如 Int, String, Point)。
  3. 变量函数成员必须以小写字母或下划线开头(如 main, println, x)。
  4. 标识符中其他字符可以是下划线 _ 、字母或数字。
  5. 在同一个 {} 内,不能重复定义相同名称的标识符。
  6. 在不同 {} 内,可以定义重名的标识符,语言会优先选择当前范围内定义的标识符。

基础类型

我们只需要一些简单的基础类型,就可以开展大部分工作。

布尔

布尔指逻辑上的值,它们只能是真或者假。它经常用以辅助判断逻辑。

在本语言中,默认的布尔为 Bool 类型,它是一个只有两个可能的值 true(真)和 false(假)的类型。

let b1 Bool = true
let b2 Bool = false
let isGreater = 5 > 3 // 结果为 true

数值类型

由于我们目前的计算机结构比较擅长计算整数,因此一个独立的整数类型有助于提升程序的运行效率。

Koral 提供了丰富的数值类型来满足不同的需求。 在 Koral 中,默认的整数为 Int 类型,它可以表示有符号整数类型数据。 在 Koral 中,浮点数使用 Float64 类型(64 位)或 Float32 类型(32 位)。

  • Int: 平台相关的有符号整数(通常是 64 位)。
  • UInt: 平台相关的无符号整数(通常是 64 位)。
  • Int8, Int16, Int32, Int64: 固定宽度的有符号整数。
  • UInt8, UInt16, UInt32, UInt64: 固定宽度的无符号整数。
  • Float32: 32 位浮点数。
  • Float64: 64 位浮点数。
let i Int = 3987349
let f Float64 = 3.14
let b UInt8 = 255

数值字面量支持使用下划线 _ 分隔数字以提高可读性:

let million = 1_000_000
let pi = 3.141_592_653

Koral 还支持二进制、八进制和十六进制整数字面量,分别使用 0b0o0x 前缀:

let bin = 0b1010          // 二进制,值为 10
let oct = 0o755           // 八进制,值为 493
let hex = 0xFF            // 十六进制,值为 255

非十进制字面量同样支持下划线分隔符:

let mask = 0xFF_FF        // 十六进制,值为 65535
let flags = 0b1010_0101   // 二进制,值为 165

注意:非十进制字面量仅支持整数,不支持浮点数。十六进制字母大小写均可(0xABcd 等价于 0xabCD)。

浮点字面量还支持使用 e 指数后缀的科学计数法:

let a = 1e3      // 1000.0
let b = 1e-3     // 0.001
let c = 2.5e+2   // 250.0
let d = 1_000e2  // 100000.0

注意:指数记法只支持小写 e,与 0b/0o/0x 前缀的小写约定保持一致。

Duration 支持整数字面量后缀:

let a = 10s
let b = 250ms
let c = 30min
let d = 2h
let e = 150us
let f = 42ns

支持的后缀为 hminsmsusns。 Duration 字面量会被降糖为 Duration.new(seconds: ..., nanoseconds: ...)。 负值保持一元负号语义(例如 -5s 会被解析为对 5s 应用一元 -)。

类型转换

不同数值类型之间需要显式转换,使用 expr(Type) 语法:

let a Int = 42
let b Float64 = a(Float64)    // Int -> Float64
let c Int32 = a(Int32)        // Int -> Int32
let d UInt8 = 255(UInt8)      // Int -> UInt8

字符串

我们并不是生活在一个只有数字的世界,所以我们也非常需要使用文字来显示我们需要的信息。

在本语言中,字符串用于表示文本数据。 String 类型,它是一个 UTF-8 编码的字符序列数据。

字符串字面量只使用双引号 ""

let s1 String = "Hello, world!"

Koral 支持字符串插值,允许在字符串中嵌入表达式,使用 \(expr) 语法:

let name = "Koral"
let count = 3
println("Hello, \(name)!")                    // Hello, Koral!
println("Count: \(count)")                    // Count: 3
println("Mixed \(name) has \(count) messages") // Mixed Koral has 3 messages
println("Sum \(1 + (2 * 3))")                 // Sum 7

转义字符使用反斜杠 \

"\n"        // 换行
"\t"        // 制表符
"\r"        // 回车
"\v"        // 垂直制表符
"\f"        // 换页
"\0"        // 空字符
"\\"        // 反斜杠
"\""        // 双引号
"\'"        // 单引号
"\x41"      // 十六进制字节转义:恰好 2 位十六进制(0x00–0xFF),如 \x41 = 'A'
"\u{41}"    // Unicode 标量转义:1–6 位十六进制,如 \u{41} = 'A',\u{1F600} = 😀

多行字符串字面量

使用 """ 定界符可以编写跨越多行的字符串,规则与 Swift 保持一致:

  • 开头的 """ 后面必须紧跟换行符。
  • 结尾的 """ 必须单独占一行,其前导空白(空格或制表符)决定所有内容行的公共缩进前缀,该前缀会被自动剥离。
  • 每行内容的缩进必须至少与结尾 """ 的缩进相同,否则报错。
  • 支持与普通字符串相同的转义序列和 \(...) 插值。
let message = """
    Hello, Koral!
    Welcome to multiline strings.
    """
// 等价于 "Hello, Koral!\nWelcome to multiline strings."

let name = "World"
let greeting = """
    Hello, \(name)!
    Have a great day.
    """
// 等价于 "Hello, World!\nHave a great day."

结尾 """ 的缩进决定剥离量:

let s = """
        indented content
        second line
    """
// 结尾 """ 缩进 4 格,内容缩进 8 格,剥离 4 格后得到:
// "    indented content\n    second line"

常用的 String 方法:

let s = "Hello, World!"
s.count()                    // 13 - 字节长度
s.is_empty()                 // false
s.contains("World")          // true
s.starts_with("Hello")       // true
s.ends_with("!")             // true
s.to_ascii_lowercase()       // "hello, world!"
s.to_ascii_uppercase()       // "HELLO, WORLD!"
s.trim_ascii()               // 去除首尾空白
s.slice(0..<5)               // "Hello" - 切片
s.find("World")              // Some(7)
s.replace_all("World", "Koral") // "Hello, Koral!"
s.split(",")                 // 按分隔符分割
s.lines()                    // 按行分割

// 拼接字符串列表
list.join_to_string(", ")   // 用分隔符拼接 List[String]

Rune 字面量

Rune 字面量使用单引号 '',表示且仅表示一个 Unicode 标量值。

let r Rune = 'A'
let nl Rune = '\n'
let smile Rune = '\u{1F600}'

Rune 字面量推断规则:

  • 默认类型为 Rune
  • 在明确期望 UInt8 的上下文中,若是单个 ASCII 字符,可收窄推断为 UInt8(byte)。

集合字面量

Koral 支持三种内置集合类型的字面量:List[T]Set[T]Dict[K, V]

let a = [1, 2, 3]                    // 默认推断为 List[Int]
let b Set[Int] = [1, 2, 3]           // 由上下文推断为 Set[Int]
let c = ["x": 1, "y": 2]             // 推断为 Dict[String, Int]
let empty List[Int] = []             // 空字面量必须有类型上下文

规则说明:

  • [e1, e2, ...] 是集合字面量;无类型上下文时默认推断为 List[T]
  • Set[T] 类型上下文中,同样的语法会推断为 Set。
  • [k1: v1, k2: v2, ...] 是 Dict 字面量,推断为 Dict[K, V]
  • [] 在无上下文时无法推断类型,必须显式标注目标类型。
  • 集合字面量和 Dict 字面量都支持尾随逗号。
  • 集合字面量仅支持内置 List / Set / Dict,不支持第三方容器类型。

引用类型 (Reference)

引用类型用于引用另一个值,而不是持有它。这在需要共享数据或避免复制时非常有用。Koral 区分托管引用和借用引用:

  • ref T — 托管只读引用。支持 .val 读取,但不支持 .val = expr 赋值。
  • ref mut T — 托管可变引用。支持 .val 读取和 .val = expr 赋值。
  • ref '_ T — 借用只读引用。运行时布局与 ref T 相同,但前端禁止逃逸。
  • ref '_ mut T — 借用可变引用。运行时布局与 ref mut T 相同,但前端禁止逃逸。
  • ref mut T 可隐式转换为 ref Tref '_ mut T 可隐式转换为 ref '_ T。反向不允许。

使用 .ref 后缀表达式创建引用。结果类型取决于源的可变性:

let mut n = 42
let a ref '_ mut Int = n.ref
let b = a.val            // 解引用,得到 42
a.val = 100              // 解引用赋值(ref mut 支持 .val = expr)

let m = 42
let c ref '_ Int = m.ref
let d = c.val            // 解引用读取,得到 42
// c.val = 100           // 错误:ref 不支持 .val 赋值

let owned ref mut Int = n.ref // 托管期望类型触发局部提升

ref / ref mutref '_ / ref '_ mut 在运行时都复用同一套 ABI、内存布局与 retain / release 行为。区别只在前端语义:借用引用不能逃逸,托管引用可以长期持有。.ref 默认先构造成借用引用;只有当前局部上下文已经明确要求托管引用时,编译器才会在当前函数内把这次构造提升为托管引用。box(expr) 则始终显式构造托管 ref mut T

指针类型同样区分只读和可变:

  • ptr T — 只读指针。支持 .val 读取,但不支持 .val 赋值和 p[i] 赋值。
  • ptr mut T — 可变指针。支持 .val 读取、.val = expr 赋值、p[i] 读取和 p[i] = expr 赋值。
  • ptr mut T 可隐式转换为 ptr T。反向转换不允许。

弱引用

弱引用不会增加引用计数,用于打破循环引用。与 ref/ref mut 一样,弱引用也区分可变性:weakref T(只读)和 weakref mut T(可变)。

使用 .weakref 后缀表达式从 ref 类型创建弱引用,使用 .to_ref() 方法尝试将弱引用升级回强引用(返回 Option 类型)。

let strong ref mut Int = box(42)

// 可变路径:ref mut → weakref mut → Option[ref mut T]
let weak = strong.weakref              // ref mut T → weakref mut T
let upgraded = weak.to_ref()           // weakref mut T → Option[ref mut T]

// 只读路径:ref → weakref → Option[ref T]
let ro ref Int = strong                // 隐式宽化
let ro_weak = ro.weakref               // ref T → weakref T
let ro_upgraded = ro_weak.to_ref()     // weakref T → Option[ref T]

内存管理

Koral 旨在提供高效且安全的内存管理。它结合了自动内存管理和手动控制的优点。

  • 值语义(Value Semantics):默认情况下,Koral 中的类型(如 Int, 结构体)具有值语义。这意味着在赋值或传递参数时,数据会被复制。
  • 引用(Reference)ref / ref mut 是可逃逸的托管引用,ref '_ / ref '_ mut 是不可逃逸的借用引用。两者运行时布局相同,retain / release 行为也相同,差异只体现在前端静态语义。.ref 默认构造借用引用;只有当前局部上下文已经明确要求托管引用时,才会发生局部提升。box(expr) 是显式托管构造。函数参数不允许隐式 ref 提升——调用方必须用 .ref 显式传入。普通参数不允许 auto-deref——传入 ref T 给期望 T 的参数需要显式用 .val。隐式转换仅对 method receiver(self)生效。Koral 使用所有权分析和逃逸分析来决定栈安全借用还是堆支持的引用计数,防止悬垂指针和内存泄漏。
  • 所有权转移(Move Semantics):对于没有执行复制操作的变量,赋值和传参操作会导致所有权转移(Move)。一旦所有权被转移,原来的变量就不能再被使用了。

操作符

操作符是一种告诉编译器执行特定的数学或逻辑操作的符号。

算术操作符

算数操作符主要被使用在数字类型的数据运算上,大部分声明符合数学中的预期。

let a = 4
let b = 2
println( a + b )    // + 加
println( a - b )    // - 减
println( a * b )    // * 乘
println( a / b )    // / 除
println( a % b )    // % 取余

比较操作符

比较操作符用于比较两个值的大小关系,结果为 Bool 类型。注意不等于使用 <> 表示。

let a = 4
let b = 2
println( a == b )     // == 等于
println( a <> b )     // <> 不等于 
println( a > b )      // > 大于
println( a >= b )     // >= 大于或等于
println( a < b )      // < 小于
println( a <= b )     // <= 小于或等于

逻辑操作符

逻辑操作符主要被用来对两个 Bool 类型的操作数进行逻辑运算(与、或、非)。

let a = true
let b = false
println( a and b )       // 与,两者同时为真才为真
println( a or b )        // 或,两者其中一者为真就为真
println( not a )         // 非,布尔值取反

其中,andor 具有短路语义。

let a = false and f() // 不会执行 f()
let b = true or f()   // 不会执行 f()

位操作符

位操作符主要用于对两个整数类型的操作数进行位运算。

let a = 4
let b = 2
println( a & b )    // 按位与
println( a | b )    // 按位或
println( a ^ b )    // 按位异或
println( ~a )       // 按位取反
println( a << b )   // 左移
println( a >> b )   // 右移

范围操作符

范围操作符用于生成一个范围(Range),常用于循环或模式匹配。

1..5     // 1 <= x <= 5 (闭区间)
1..<5    // 1 <= x < 5  (右开区间)
1<..5    // 1 < x <= 5  (左开区间)
1<..<5   // 1 < x < 5   (开区间)
1..      // 1 <= x      (右无界,包含起始)
1<..     // 1 < x       (右无界,不含起始)
..5      // x <= 5      (左无界,包含结束)
..<5     // x < 5       (左无界,不含结束)
..       // 全范围

复合赋值

Koral 支持常见的算术复合赋值,而且同时支持位运算复合赋值。

let mut x = 10
x += 5       // x = x + 5
x -= 2       // x = x - 2
x *= 3       // x = x * 3
x /= 2       // x = x / 2
x %= 4       // x = x % 4

let mut y = 12
y &= 10     // y = y & 10
y |= 1      // y = y | 1
y ^= 15     // y = y ^ 15
y <<= 1     // y = y << 1
y >>= 2     // y = y >> 2

运算符重载

Koral 支持基于 Trait 的运算符重载,主要覆盖算术和比较操作。下标语法是内建语义,不支持用户自定义重载。

内建映射如下:

  • + -> Add[R],对应 add(self, other R) Self
  • -(二元)-> Sub[R],对应 sub(self, other R) Self
  • -(一元)-> Neg,对应 neg(self) Self
  • * -> Mul[R],对应 mul(self, other R) Self
  • / -> Div[R],对应 div(self, other R) Self
  • % -> Rem[R],对应 rem(self, other R) Self
  • == / <> -> Eq,对应 equals(self, other Self) Bool
  • < / > / <= / >= -> Ord,对应 compare(self, other Self) Int

位运算符(&|^~<<>>)目前仍是内建操作,不通过公开的运算符 Trait 自定义。

type Vec2(x Int, y Int)

given Vec2 as Add[Vec2] {
    add(self, other Vec2) Vec2 = Vec2(self.x + other.x, self.y + other.y)
}

given Vec2 as Neg {
    neg(self) Vec2 = Vec2(-self.x, -self.y)
}

given Vec2 as Eq {
    equals(self, other Vec2) Bool = self.x == other.x and self.y == other.y
}

given Vec2 as Ord {
    compare(self, other Vec2) Int =
        if self.x <> other.x then self.x.compare(other.x) else self.y.compare(other.y)
}

let sum = Vec2(1, 2) + Vec2(3, 4)
let flipped = -sum
let same = sum == Vec2(4, 6)
let ordered = Vec2(1, 0) < Vec2(2, 0)

内建下标规则:

let mut list = [10, 20, 30]
println(list[0])
list[1] = 99

let text = "abc"
let b UInt8 = text[1]

let p ptr mut Int = alloc_memory[Int](2)
p[0] = list[0]
let first = p[0]
dealloc_memory(p)
  • value[key]value[key] = expr 只支持 StringList[T]Deque[T]ptr Tptr mut T
  • String[key] 返回 UInt8 字节值,只读且不可取地址。
  • List[T]Deque[T] 支持值读取、赋值、深层 place 更新,以及显式/隐式 ref / ref mut 上下文。
  • ptr T 只支持读取;ptr mut T 同时支持读取和写入。
  • 用户自定义类型不能通过 Trait 获得 [] 能力,泛型约束也不能为类型添加下标能力。

值合并与可选链

Koral 提供了三个特殊的操作符用于处理 OptionResult 类型:

  • or else:值合并,当左侧为 NoneError 时返回右侧的默认值。
  • and then:可选链/值变换,当左侧为 SomeOk 时对内部值应用右侧的变换。
  • or return:提前返回传播语法糖。当左侧为 Some / Ok 时解包其值;当左侧为 None / Error 时从外围函数提前返回。

and thenor else 表达式中,关键字 it 用于引用被解包的值:对于 and thenit 是内部的 SomeOk 值;对于 Result 类型的 or elseitError 值。

let opt = Option[Int].Some(42)
let val = opt or else 0           // 42(因为 opt 是 Some)

let none = Option[Int].None()
let val2 = none or else 0         // 0(因为 none 是 None)

let mapped = opt and then it * 2   // Some(84)

let load_port(path String) Result[Int] = {
    let text = read_text_file(path) or return
    parse_int(text)
}

or return 等价于固定形式的 or else 提前返回:

  • Resultexpr or return 等价于 expr or else { return .Error(it) }
  • Optionexpr or return 等价于 expr or else { return .None() }

它只能用在返回类型种类匹配的外围函数里:

  • 传播 Result 时,外围函数必须返回 Result
  • 传播 Option 时,外围函数必须返回 Option

运算符优先级

操作符优先级从高到低如下:

  1. 后缀: 调用 (), 下标 [], 成员访问 ., 存储修饰 .ref/.ptr/.val, 限定/泛型方法后缀
  2. 前缀: 一元 -, ~
  3. 乘除: *, /, %
  4. 加减: +, -
  5. 移位: <<, >>
  6. 比较: ==, <>, <, >, <=, >=
  7. 范围: .., ..<, <.., <..<
  8. 按位与: &
  9. 按位异或: ^
  10. 按位或: |
  11. 模式测试: is, is not
  12. 逻辑非: not
  13. 逻辑与: and
  14. 可选链: and then
  15. 逻辑或: or
  16. 值合并: or else
  17. 提前返回传播: or return

选择结构

选择结构用于判断给定的条件,根据判断的结果来控制程序的流程。

在 Koral 中选择结构使用 if 语法表示,if 后面紧跟判断条件,在条件为 true 时执行条件后面的 then 分支,在条件为 false 时执行 else 关键字后面的 else 分支。

例如:

let main() Void = if 1 == 1 then println("yes") else println("no")

执行上面的程序会看到 yes

if 同样也是表达式,thenelse 分支后面都必须是表达式,根据 if 的条件,if 表达式的值可能是 thenelse 分支其中的一个。

因此上面那段程序我们也可以这样写,两种写法结果等价。

let main() Void = println(if 1 == 1 then "yes" else "no")

由于 if 本身也是表达式,因此 else 后面自然也可以接另外一个 if 表达式,这样我们就可以实现连续的条件判断。

let x = 0
let y = if x > 0 then "bigger" else if x == 0 then "equal" else "less"

当我们不需要处理 else 分支时,可以省略 else 分支,这时它的值是 Void

let main() Void = if 1 == 1 then println("yes")

if 分支 body 是块时,这个块本身仍然默认是 Void。如果要让该分支给外层 if 表达式产值,需要在分支 body 中使用 yield;这也提供了分支内的 early exit:

let label = if score >= 90 then {
    if score == 100 then yield "perfect"
    yield "A"
} else {
    yield "other"
}

如果在分支 body 里再写语句形态的嵌套 if / when,其中的 yield 仍然指向当前外层分支表达式;而嵌套的 if / when 表达式会拥有各自独立的 yield 目标。

if is 模式匹配

if 还支持 is 模式匹配语法,可以在条件判断的同时解构值:

let opt = Option[Int].Some(42)
if opt is .Some(v) then {
    println(v)  // 42
} else {
    println("None")
}

多个条件现在统一使用 and / or / not 组合;当左侧 is 发生绑定时,后续 and 子句和 then 分支可以继续使用这些绑定:

if foo() is .A(x) and bar(x) is .B(y) and y > 0 then {
    println(y)
} else {
    println("no match")
}

条件组合规则:

  • and / or / not 按普通布尔逻辑短路求值。
  • 前面 is 子句绑定的变量可用于后续 and 子句与 then 分支。
  • 带绑定的 is 不允许出现在 or 分支下,也不允许被 not 包裹。

循环结构

循环结构是指在程序中需要反复执行某个功能而设置的一种程序结构。

while 语句

在 Koral 中循环结构使用 while 语法表示,while 后面紧跟判断条件,在条件为 true 时执行后面的语句 body,然后重新回到判断条件处进行判断进入下一轮循环,在条件为 false 结束循环。while 只能作为语句使用。

let mut i = 0
while i < 10 then {
    println(i)
    i += 1
}

while is 模式匹配

while 也支持 is 模式匹配,常用于迭代器循环:

let mut iter = list.iterator()
while iter.next() is .Some(v) then {
    println(v)
}

while 中同样可以把带绑定的 is 与后续条件用 and 连接:

while iter.next() is .Some(item) and parse(item) is .Ok(v) then {
    println(v)
}

while 条件中,and 链仍然按从左到右短路求值;任一子句失败时,循环结束。

break 和 continue

  • break: 退出循环。
  • continue: 跳过当前迭代。
let mut i = 0
while true then {
    if i > 20 then break
    if i % 2 == 0 then { i += 1; continue }
    println(i)
    i += 1
}

for 循环

for 循环用于遍历任何实现了迭代器接口的对象(如列表、Dict、Set、范围等)。

每次迭代,迭代器产生的下一个值会尝试匹配 pattern,如果匹配成功,则执行 then 后面的语句 body。for 只能作为语句使用。

let nums List[Int] = [10, 20, 30]
for x in nums then {
    println(x)
}

for i in 0..5 then {
    println(i)
}

finally 语句

finally 语句用于声明在当前块作用域退出时执行的清理表达式。无论作用域是正常退出还是通过 returnbreakcontinue 提前退出,finally 表达式都会被执行。

当执行 Never 终止路径(例如 panic()abort()exit())并导致程序直接终止时,不保证执行当前作用域中的 finally

finally 后面跟一个表达式,该表达式的返回值会被丢弃。

let main() Void = {
    println("start")
    finally println("cleanup")
    println("work")
    // 输出: start, work, cleanup
}

同一作用域内的多个 finally 按声明的逆序(LIFO)执行:

let main() Void = {
    finally println("first")
    finally println("second")
    finally println("third")
    // 输出: third, second, first
}

finally 绑定到声明它的块作用域,而非函数作用域。在循环中,finally 会在每次迭代结束时执行:

let mut i = 0
while i < 3 then {
    i += 1
    finally println("cleanup")
    println(i)
    // 每次迭代输出: i 的值, cleanup
}

finally 表达式也可以是块表达式:

finally {
    println("cleaning up")
    close(handle)
}

限制

  • finally 表达式内部不允许使用 returnbreakcontinueyield
  • finally 表达式内部不允许嵌套 finally
  • finally 不是异常栈展开机制;在 panic/abort/exitNever 终止路径上不保证执行。
  • 以上限制不穿透 Lambda 边界——Lambda 内部拥有独立的作用域。

模式匹配

Koral 拥有强大的模式匹配功能,主要通过 when 表达式和 is 操作符使用。

when 表达式

when 表达式允许你将一个值与一系列模式进行比较,并根据匹配的模式执行相应的代码。它类似于其他语言中的 switch 语句,但功能更为强大。when 也是一个表达式,会返回匹配分支的值。

let x = 5
let result = when x in {
    1 then "one",
    2 then "two",
    _ then "other",
}

if 一样,when 的分支如果写成块,这个块本身仍然默认是 Void。要给外层 when 表达式产值,需要在分支 body 中使用 yield,并且可以在分支内部提前退出:

let label = when score in {
    100 then {
        println("bonus")
        yield "perfect"
    },
    >= 90 then {
        if has_curve(score) then yield "A+"
        yield "A"
    },
    _ then { yield "other" },
}

支持的模式包括:

  • 通配符模式:_(匹配任意值)
  • 字面量模式:1, "abc", 'a', true
  • 变量绑定模式:x(匹配任意值并绑定到 x),mut x(可变绑定)
  • 比较模式:> 5, < 0, >= 10, <= -1
  • 结构体解构模式:Point(x, y), Rect(Point(a, b), w, h)
  • Pair 解构模式:(a, b)(等价于 Pair(a, b) 模式)
  • 枚举模式:.Some(v), .None
  • 逻辑模式:pattern and pattern, pattern or pattern, not pattern
// 枚举匹配
type Shape {
    Circle(radius Float64),
    Rectangle(width Float64, height Float64),
}

let area = when shape in {
    .Circle(r) then 3.14 * r * r,
    .Rectangle(w, h) then w * h,
}

// 比较模式
let grade = when score in {
    >= 90 then "A",
    >= 80 then "B",
    >= 70 then "C",
    _ then "F",
}

// 逻辑模式
when x in {
    1 or 2 or 3 then println("small"),
    _ then println("big"),
}

// 结构体解构模式
type Point(x Int, y Int)
type Rect(origin Point, width Int, height Int)

let p = Point(10, 20)
when p in {
    Point(x, y) then println(x + y),  // 30
}

// 嵌套结构体解构
let r = Rect(Point(1, 2), 30, 40)
when r in {
    Rect(Point(a, b), w, h) then println(a + b + w + h),  // 73
}

// 在 if...is 中使用结构体解构
if p is Point(x, y) then {
    println(x * y)  // 200
}

// 通配符和字面量字段匹配
when p in {
    Point(0, y) then println(y),       // 第一个字段为 0 时匹配
    Point(_, y) then println(y),       // 忽略第一个字段
}

// 泛型结构体解构
type Box[T Any](val T)
let b = Box[Int](42)
when b in {
    Box(v) then println(v),  // 42
}

is 操作符

is 操作符用于检查一个值是否匹配某个模式,结果为 Bool 类型。它现在是一个通用表达式,可以出现在 let 初始化、函数返回值、函数参数等任意表达式位置。

is not 是对应的否定形式,结果等价于对匹配结果取反。

当在 ifwhile 等条件表达式中使用时,is 在匹配成功后还可以将模式中的变量绑定到当前作用域;但在其他位置,is 只能做纯布尔测试,不能绑定变量。

let opt = Option[Int].Some(42)
let has_value = opt is .Some(_)
let is_empty = opt is not .Some(_)

if opt is .Some(v) then {
    println(v)  // 42
}

// 比较模式
if score is >= 60 then {
    println("passed")
}

// 条件中可继续组合普通布尔逻辑
if opt is .Some(v) and v > 0 then {
    println(v)
}

函数

函数是用来完成特定任务的独立的代码块。

定义

函数通过 let 关键字定义,函数的名字后面使用 () 表示这个函数接受的参数,括号后面是这个函数的返回类型。命名函数和方法都必须显式写出返回类型。

函数的 = 右边必须声明一个表达式,这个表达式的值就是函数的返回值。

let f1() Int = 1
let f2(a Int) Int = a + 1
let f3(a Int) Int = a + 1

调用

使用 () 语法调用函数:

let a = f1()
let b = f2(1)

参数

参数是函数执行时能够接收的数据。使用 参数名 类型 声明参数。

let add(x Int, y Int) Int = x + y
let a = add(1, 2) // a == 3

可变参数使用 mut 关键字标记:

let increment(mut x Int) Int = { x += 1; return x }

对于普通参数,mut 只表示函数体内部这个形参绑定可重新赋值。它不是函数签名的一部分,不改变 Func[...] 类型,也不会参与 trait/given 方法满足性的判断。

命名参数

命名参数遵循以下规则:

  • 命名参数声明形式为 name: Type
  • 调用时必须以 name: expr 的形式传递。
  • 每个参数独立决定使用命名形式还是位置形式,因此允许混合签名。
  • 命名参数仍然保持固定顺序,不支持重排。
  • 命名参数不支持默认值。
// 混合位置参数和命名参数
let create_rect(x Int, y Int, width: Int, height: Int) Rect = todo()
create_rect(10, 20, width: 100, height: 200)

// 全部命名参数
let connect(host: String, port: Int) Void = todo()
connect(host: "localhost", port: 8080)

调换命名参数顺序或省略标签会导致编译错误。

命名参数同样适用于结构体和枚举:

type Button(width: Int, height: Int, label: String)
let b = Button(width: 100, height: 50, label: "OK")

type Shape {
    Circle(radius: Float64),
    Line(start Point, end: Point),  // 混合使用
}
let s = Shape.Line(Point(0, 0), end: Point(1, 1))

在模式匹配中,命名参数位置需要使用 name: pattern 语法,保持构造与解构的对称性:

when s in {
    .Circle(radius: r) then println(r),
    .Line(s, end: e) then println(s.x),
}
if b is Button(width: w, height: _, label: l) then println(l)

Trait 方法可以使用命名参数。实现时必须与 trait 声明完全一致(相同名称、相同位置):

trait Drawable {
    draw(self ref, at_x: Int, at_y: Int) Void
}
given MyType as Drawable {
    draw(self ref, at_x: Int, at_y: Int) Void = todo()  // 必须匹配
}

函数类型(Func)不携带命名参数标签,lambda 参数始终使用位置参数语法:

let f Func[String, Int, Void] = (host String, port Int) -> {
    connect(host: host, port: port)
}
f("localhost", 8080)

Foreign 声明(foreign letforeign type)不支持命名参数。

函数类型

在 Koral 中,函数也是一种类型。函数的类型使用 Func[T1, T2, ..., R] 语法声明,其中 T1, T2, ... 是参数类型,R 是返回类型。

let sqrt(x Int) Int = x * x          // Func[Int, Int]
let f Func[Int, Int] = sqrt
let a = f(2)                      // a == 4

利用这个特性,我们也可以定义函数类型的参数或者返回值。

let hello() Void = println("Hello, world!")
let run(f Func[Void]) Void = f()
let toRun() Func[Func[Void], Void] = run

let main() Void = toRun()(hello)

Lambda 表达式

Lambda 表达式与函数定义很相似,只是 = 换成了 ->,并且没有函数名和 let 关键字。

let f1(x Int) Int = x + 1            // Func[Int, Int]
let f2 = (x Int) Int -> x + 1        // Func[Int, Int]
let a = f1(1) + f2(1)                // a == 4

在上下文中可以得知 lambda 的类型时,可以省略参数类型和返回类型:

let f Func[Int, Int] = (x) -> x + 1

Lambda 支持多种形式:

() -> 42                           // 无参数
(x) -> x * 2                      // 单参数,类型推断
(x Int) -> x * 2                  // 单参数,显式类型
(x, y) -> x + y                   // 多参数,类型推断
(x Int, y Int) Int -> x + y       // 完整类型标注
(x) -> { let y = x * 2; return y + 1 }  // 块体

闭包

Lambda 表达式可以捕获其周围作用域中的变量,这被称为闭包。

let make_adder(base Int) Func[Int, Int] = {
    return (x) -> base + x
}

let add10 = make_adder(10)
let result = add10(32)  // result == 42

捕获规则

闭包可以捕获不可变变量和可变变量(let mut)。可变变量被捕获时采用引用捕获,因此闭包内的修改对外部可见。

let x = 10
let f = () -> x + 1  // OK: x 是不可变的

let mut counter = 0
let increment = () -> { counter = counter + 1 }  // OK: let mut 通过引用捕获
increment()
// counter 现在是 1

柯里化

闭包使柯里化成为可能:

let add Func[Int, Func[Int, Int]] = (x) -> (y) -> x + y

let add10 = add(10)
let result = add10(32)  // result == 42
let sum = add(20)(22)   // sum == 42

数据类型

数据类型是由一系列具有相同类型或不同类型的数据构成的数据集合,它是一种复合数据类型。

Koral 提供了强大的类型系统,允许你定义自己的数据结构。使用 type 关键字来定义。

结构体 (Product Type)

结构体用于将多个相关的值组合在一起。每个字段都有一个名称和类型。

定义

type Empty()
type Point(x Int, y Int)

构造

使用 () 语法调用构造器:

let a Point = Point(0, 0)

使用成员变量

使用 . 语法访问成员变量:

type Point(x Int, y Int)

let main() Void = {
    let a = Point(64, 128)
    println(a.x)  // 64
    println(a.y)  // 128
}

可变成员变量

成员变量默认是只读的。使用 mut 关键字标注可变成员变量:

type Point(mut x Int, mut y Int)

let main() Void = {
    let a = Point(64, 128)
    a.x = 2  // ok,因为 x 是 mut
    a.y = 0  // ok,因为 y 是 mut
}

成员变量的可变性跟随类型定义,与实例变量是否可变无关。

枚举 (Sum Type)

枚举允许你定义一个类型,它可以是几个不同变体(Variant)中的一个。每个变体可以携带不同类型的数据。

type Shape {
    Circle(radius Float64),
    Rectangle(width Float64, height Float64),
}

let s = Shape.Circle(1.0)

使用枚举类型值

通过模式匹配来提取枚举类型变体中携带的数据:

let area = when s in {
    .Circle(r) then 3.14 * r * r,
    .Rectangle(w, h) then w * h,
}

隐式成员表达式

隐式成员表达式使用 .memberName(...) 形式。

规则如下:

  • 仅当上下文已经给出期望类型时才合法。
  • 可用于构造枚举变体,也可用于调用静态方法。
  • 必须带有括号;裸 .Name 不是合法的隐式成员表达式。
  • 若编译器无法从上下文推断期望类型,则该表达式会被拒绝。
// 枚举构造 — 省略 Option[Int] 前缀
let a Option[Int] = .Some(42)
let b Option[Int] = .None()

// 函数参数中使用
let process(opt Option[Int]) Void = when opt in {
    .Some(v) then println(v.to_string()),
    .None() then println("none"),
}
process(.Some(10))

// 赋值中使用
let mut x Option[Int] = .None()
x = .Some(100)

// 条件表达式分支中使用
let c Option[Int] = if true then .Some(1) else .None()

// 静态方法调用 — 省略 List[Int] 前缀
let list List[Int] = .new()
let list2 List[Int] = .with_capacity(10)

类型别名 (Type Alias)

类型别名允许你为已有类型定义一个新名称,提高代码可读性。使用 type AliasName = TargetType 语法声明。

type Meters = Int
type Coord = Point
type IntList = List[Int]

类型别名在编译时被完全消除,别名与目标类型完全等价:

type Meters = Int

let distance Meters = 100
let add_meters(a Meters, b Meters) Meters = a + b
let result = add_meters(distance, 50)  // result == 150

别名可以链式定义:

type Meters = Int
type Distance = Meters  // Distance 最终解析为 Int

类型别名支持访问修饰符:

public type Meters = Int       // 公开
private type InternalId = Int  // 仅文件内可见

限制:

  • 类型别名不支持泛型参数(如 type Alias[T] = List[T] 不合法),但目标类型可以是泛型实例化类型(如 type IntList = List[Int])。
  • 不允许循环引用(如 type A = A)。
  • 类型别名名称必须以大写字母开头。

Trait 与 Given

Koral 采用 Trait(特征)来定义共享的行为。这类似于其他语言中的接口(Interface)或类型类(Type Class)。

定义 Trait

Trait 定义了一组方法签名,任何实现了该 Trait 的类型都必须提供这些方法的具体实现。

trait Printable {
    to_string(self ref) String
}

Trait 支持继承,使用父 Trait 名称声明:

trait Ord Eq {
    compare(self, other Self) Int
}

多个父 Trait 使用 and 连接:

trait MyTrait Eq and Hash {
    my_method(self) Int
}

实现 Trait (Given)

使用 given Type as Trait { ... } 实现块为特定类型实现 Trait:

trait Eq {
    equals(self, other Self) Bool
}

trait Ord Eq {
    compare(self, other Self) Int
}

type Point(x Int, y Int)

given Point as Eq {
    equals(self, other Point) Bool = self.x == other.x and self.y == other.y
}

given Point as Ord {
    compare(self, other Point) Int = self.x - other.x
}

说明:

  • given Type as Trait 是显式实现入口。
  • 父子 Trait 采用分级实现:实现 Ord 不会自动得到 Eq,需要分别写实现块。

Trait 工具方法(given Trait

Koral 支持 given Trait { ... } 用于定义 Trait 的工具方法。

规则如下:

  • trait 中声明的方法是 requirement 方法(用于一致性检查与 trait object 的动态分发)。
  • given Trait 中声明的方法是 工具方法(人体工学辅助方法),作为 requirement witness。
  • 工具方法不会合并到具体类型的方法集中;它们按调用点可见性参与解析。

示例:

trait Eq {
    equals(self, other Self) Bool
}

given Eq {
    not_equals(self, other Self) Bool = not self.equals(other)
}

type Num(x Int)

given Num as Eq {
    equals(self, other Num) Bool = self.x == other.x
}

let a = Num(1)
let b = Num(2)
println(a.not_equals(b))

带块级约束的工具方法:

trait Iterator[T Any] {
    next(self ref mut) Option[T]
}

given[T Ord] Iterator[T] {
    max(self) Option[T] = ...
    min(self) Option[T] = ...
}

// 对实现了 Iterator[Int] 的类型,max/min 可用

分发规则:

  • requirement 方法:在泛型约束与 trait object 场景下走 witness/vtable 动态分发。
  • 工具方法(given Trait):走静态分发,不进入虚调用入口。

工具方法可在以下场景使用:

  • 泛型约束上下文(如 [T Trait]
  • trait object 上下文
  • 已显式实现该 Trait 的具体类型

显式消歧调用

当出现同名候选冲突时,可使用限定调用:

  • 实例方法:object.(TraitName)method(...)
  • 静态方法:Type.(TraitName)method(value, ...)
  • 泛型实例方法:object.(TraitName)method[TypeArgs...](...)
  • 泛型静态方法:Type.(TraitName)method[TypeArgs...](value, ...)

其中泛型方法要求 trait 限定写在方法类型参数之前。

覆盖与冲突规则

  • 类型自身方法优先于工具方法。
  • 多个 trait 工具来源产生同签名时,Koral 不做隐式优先级选择。
  • 必须显式消歧(或消除冲突声明)。
  • given Trait 中不允许与该 trait requirement 同名同签名的方法。

模块边界规则

边界锚定规则:

  • given Trait { ... } 仅允许在 Trait 所在 root 模块子树内声明与使用。
  • given Type { ... } 仅允许在 Type 所在 root 模块子树内声明与使用。
  • given Type as Trait { ... } 遵循孤儿规则:Type 或 Trait 至少一个为当前 root 模块本地定义。
  • 不允许跨 crate 注入。

扩展方法

given 块不仅可以用于实现 Trait,还可以直接用于为类型添加方法:

given Point {
    public distance(self) Float64 = {
        let dx = self.x(Float64)
        let dy = self.y(Float64)
        return dx + dy // ...
    }
    
    // 不包含 self 的方法,通过类型名调用
    public origin() Point = Point(0, 0)
}

let p = Point.origin()

标准库核心 Trait

常用核心 Trait 主要包括:

  • Add[R] / Sub[R] / Neg / Mul[R] / Div[R] / Rem[R]:算术运算符 Trait。
  • Eq / Ord:相等性与排序比较。
  • Hash:Dict/Set 键的哈希支持。
  • ToString:字符串转换。
  • Iterator[T]:迭代协议(next(self ref mut) Option[T])。
  • Error:错误消息接口(message(self ref) String)。
  • Drop:析构钩子(drop(source ptr mut Self) Void)。

算术和比较操作符会在语义阶段降级为对应的 trait 方法(例如 + 对应 Add)。下标访问由编译器内建规则处理,不属于公开 trait 系统。

Drop.drop 是编译器保留的析构入口,不作为普通用户方法直接调用。它接收的是一块已拥有对象存储的地址 source ptr mut SelfDrop 实现允许包含复合字段,不再要求字段必须是 primitive 形状。

Trait Object

Trait Object 是 Koral 中实现运行时多态(动态派发)的机制。通过 ref TraitNameref mut TraitName 语法,可以将实现了某个 Trait 的任意类型擦除为统一的引用类型。

基本语法

Trait object 构造遵循以下规则:

  • 目标类型写作 ref TraitNameref mut TraitName
  • 源值必须实现该 Trait,并在期望该 trait object 类型的上下文中发生转换。
  • box(...) 是提供拥有值并完成此类转换的标准方式。
trait Drawable {
    draw(self ref) String
    reset(self ref mut) Void
}

type Circle(mut radius Int)
type Square(mut side Int)

given Circle as Drawable {
    public draw(self ref) String = "Drawing circle"
    public reset(self ref mut) Void = {
        self.radius = 0
    }
}
given Square as Drawable {
    public draw(self ref) String = "Drawing square"
    public reset(self ref mut) Void = {
        self.side = 0
    }
}

// 创建 trait object
let shape ref Drawable = box(Circle(10))
let mutable_shape ref mut Drawable = box(Square(4))

// 通过 trait object 调用方法(动态派发)
shape.draw()  // "Drawing circle"
mutable_shape.reset()
mutable_shape.draw()

通过 trait object 调用时,接收者可变性规则与普通引用一致:

  • ref TraitName 只能调用声明为 self ref 的 requirement。
  • ref mut TraitName 可以调用 self ref mutself ref 的 requirement。
  • ref TraitName 不能调用 self ref mut 的 requirement。

对象安全性

只有满足以下条件的 Trait 才能用作 trait object:

  • 方法不能有泛型参数
  • 若存在接收者,则接收者必须是 self refself ref mut
  • 方法的参数和返回值中不能出现 Self 类型(仅接收者中的该用法例外)
// 对象安全 — 可以用作 trait object
trait Error {
    message(self ref) String
}

// 不是对象安全 — 不能用作 trait object
trait Eq {
    equals(self, other Self) Bool  // Self 出现在参数中
}

trait Resettable {
    reset(self) Void  // 按值 self 不是对象安全的
}

Trait object(ref TraitNameref mut TraitName)不支持直接 .val,应通过动态派发调用 trait 方法。

泛型

泛型允许你编写适用于多种类型的代码,从而提高代码的复用性。

泛型数据类型

泛型数据类型使用 类型名[T Constraint] 语法定义泛型参数:

type Pair[T1 Any, T2 Any](left T1, right T2)

构造泛型数据类型时,在泛型参数的位置传入实际的类型:

let a1 = Pair[Int, Int](1, 2)
let a2 = Pair[Bool, String](true, "hello")

当上下文类型明确时,可以省略泛型类型参数:

let a1 = Pair(1, 2)           // 推断为 Pair[Int, Int]
let a2 = Pair(true, "hello")  // 推断为 Pair[Bool, String]

Pair 也支持字面量写法:

let p1 = (1, 2)               // 等价于 Pair(1, 2)
let p2 = (true, "hello")     // 等价于 Pair(true, "hello")

泛型函数

泛型函数在函数名后面声明类型参数:

let identity[T Any](x T) T = x

println(identity(42))       // 42
println(identity("hello"))  // hello

泛型约束

泛型参数可以指定 Trait 约束,限制可接受的类型:

let max_val[T Ord](a T, b T) T = if a > b then a else b
let contains[T Eq](list List[T], value T) Bool = list.contains(value)

多个约束使用 and 连接:

let describe[T ToString and Hash](value T) String = value.to_string()

约束也可以使用泛型 trait 形式(例如 Iterator[T]):

let consume[I Iterator[Int]](iter I) Void = {}

泛型方法

given 块中也可以定义泛型方法:

given[T Any] Option[T] {
    public map[U Any](self, f Func[T, U]) Option[U] = self and then f(it)
}

标准库最小常用示例

日常开发最常用的标准库构件可以先记这几类:

// List
let nums List[Int] = [1, 2, 3]

// Dict
let scores Dict[String, Int] = ["alice": 10, "bob": 8]

// Set
let tags Set[String] = ["koral", "lang"]

// Option + or else / and then
let port = Option[Int].Some(8080) or else 80
let doubled = Option[Int].Some(21) and then it * 2

// or return
let read_number(path String) Result[Int] = {
    let text = read_text_file(path) or return
    parse_int(text)
}

// Result(错误端为 ref Error)
let ok = Result[Int].Ok(42)
let err = Result[Int].Error(box("failed"))

完整 API 请查看 docs/std/ 下各模块文档。

模块系统

Koral 提供了强大的模块系统,用于在多个文件和目录中组织代码。

模块概念

Koral 中的模块koral.json(标准库则为 std/koral.json)里显式声明的构建单元。一个模块由入口文件以及通过 using "path" 合并进来的文件组成。

  • 目标模块:由 --target-module 选择的模块
  • 同包模块:同一个 package 中的其它 manifest 模块
  • 外部模块:来自 std 或其它依赖包的模块
  • manifest 顶层 entry:默认目标模块名,例如 app::main

入口文件命名约束:

  • 模块入口文件名(不含扩展名)必须以小写字母开头。
  • 后续字符只能使用小写字母、数字或 _
  • 源码里的模块名来自 manifest,并使用 :: 作为分隔符(例如 app::modelsstd::io)。

Using 声明

using 关键字用于文件合并和显式符号导入。所有 using 声明必须出现在文件开头,在任何其他声明之前。

文件合并

使用字符串语法将另一个文件合并到当前模块作用域:

using "utils"        // 将 utils.koral 合并到当前模块
using "./helpers"    // 支持相对路径
using "../shared/format"

文件合并规则:

  1. 路径相对于当前文件所在目录解析。
  2. 字符串表示不带 .koral 后缀的源码路径。
  3. 允许使用 ... 等相对路径段。
  4. 文件合并不会创建命名空间、别名或导出面。
  5. 合并后的文件共享同一模块作用域,因此 protected 声明可在该模块内的其它文件中访问。

模块符号导入

使用带花括号的显式语法从其它模块导入当前可见的符号:

using std::io { Reader }
using std::json { parse, Value }
using std::io { Reader as IoReader, Writer }
using std::io { .. }

说明:

  1. using module { symbol-list } 只导入对当前文件可见的符号:任意 package 的 public,以及同一 package 内的 protected public
  2. as 作用于单个导入符号,而不是整个模块。
  3. using module { .. } 导入该模块对当前文件全部可见的符号,且 .. 必须单独出现。
  4. 导入名只在当前文件内可见,不会自动再导出。
  5. 模块合法性由 manifest 中的 requires 决定,编译器不会再从目录结构里推断模块树。
  6. std 包默认可见 std,应用或测试包不应再手写对 std 的依赖。
  7. 模块导入不会把模块名绑定成命名空间对象。用 using std::io { Reader } 导入后只能写 Reader,不能写 std.io.Readerio.Reader

访问修饰符

Koral 提供四种访问级别来控制符号可见性:

修饰符可见性
public任何地方都可访问
protected public同一 package 内任意 module 可访问
protected当前逻辑 module 内可访问
private仅在同一文件内可访问

protected public 是一个复合访问修饰符,只接受精确顺序 protected publicpublic protected 非法。

package 边界按 manifest 图划分:根 package、std、以及每个 dependency package 各自独立。

默认访问级别

声明类型默认值
全局函数、变量、类型protected
结构体字段public
枚举类型构造器字段public
成员函数(given 块内)protected
Trait 方法public

直接结构体构造 Type(...) 仅在调用点对相关字段都可见时才允许。 若类型包含当前不可见的 private / protected / protected public 字段,请使用公开的工厂方法进行构造。

项目结构示例

my_project/
├── koral.json
├── main.koral           # app::main 入口
├── utils.koral          # 合并到 app::main
├── models/
│   ├── models.koral     # app::models 入口
│   └── user.koral       # 合并到 app::models
└── services/
    └── services.koral   # app::services 入口
{
  "name": "MyProject",
  "version": "0.1.0",
  "entry": "app::main",
  "modules": {
    "app::main": {
      "entry": "main.koral",
      "requires": ["app::models", "app::services"],
      "links": []
    },
    "app::models": {
      "entry": "models/models.koral",
      "requires": [],
      "links": []
    },
    "app::services": {
      "entry": "services/services.koral",
      "requires": ["app::models"],
      "links": []
    }
  }
}
// main.koral
using "utils"
using app::models { User }
using app::services { authenticate }
using std { .. }

public let main() = {
    let user = User.new("Alice")
    if authenticate(user) then {
        println("Welcome!")
    }
}

FFI (外部函数接口)

Koral 支持通过 foreign 关键字与 C 语言互操作。

链接外部库

原生库通过 koral.json / std/koral.json 中的 links 字段声明:

{
  "modules": {
    "app::main": {
      "entry": "main.koral",
      "requires": [],
      "links": ["m"]
    }
  }
}

编译器会根据解析后的 manifest 图追加链接参数。libc 默认隐式链接,无需声明。

Foreign 函数

声明外部 C 函数:

foreign let sin(x Float64) Float64
foreign let exit(code Int) Never
foreign let abort() Never

Foreign 类型

声明外部 C 类型:

// 不透明类型(无字段)
foreign type CFile {}

// 带字段的 FFI 结构体(与 C 布局对齐)
foreign type KoralTimespec(tv_sec Int64, tv_nsec Int64)

Intrinsic

intrinsic 关键字用于声明由编译器内置实现的类型和函数:

public intrinsic type Int
public intrinsic let is_unique_mutable[T Any](r ref T) Bool