跳到主要内容

v0.2.3 · 技术预览

探索所有权,让内存管理各司其职。

Riddle 用移动语义和借用检查在编译期消除内存错误,用过程间逃逸分析把不越过栈帧的值留在栈上。不写一行生命周期标注,也不为垃圾回收付出全程代价。

main.rid
struct Point {
    x: i32,
    y: i32,
}

fun distance_squared(point: Point) -> i32 {
    point.x * point.x + point.y * point.y
}

fun main() {
    let point = Point { x: 3, y: 4 };
    let value = distance_squared(point);
    print(value)
}
生命周期标注
0 生命周期标注
个运行时 ABI 函数
5 个运行时 ABI 函数
个编译阶段
9 个编译阶段
后端输出
C11 后端输出

语言特性

为什么是 Riddle

所有权、逃逸分析和确定性析构组合在一起,让安全既不靠标注,也不靠全程 GC。

所有权在编译期就说清楚了

赋值、传参和返回都会转移所有权。move checker 检查移动后使用、借用冲突,以及借用期间的赋值与移动——这些都发生在编译期,不需要任何运行时检查。

struct Foo {
    x: i32,
    y: i32,
}

fun main() {
    let a = Foo { x: 1, y: 1 };
    let b = a;
    print(a); // error: a 已经被移动
    print(b);
}

标量、共享引用、原始指针和命名函数项默认可复制;&mut T 与闭包值不可复制。

  • 用户类型实现 std::marker::Copy 即可进入复制语义,编译器会验证所有字段与枚举 payload
  • 字段访问本身不移动整个结构体;match 解构按字段记录部分移动,未移动的兄弟字段仍可继续使用
  • Option<T>Result<T, E> 仅在 payload 全部实现 Copy 时才是 Copy

不写生命周期,也能返回引用

Riddle 没有生命周期语法。编译器用过程间不动点的逃逸分析,计算每个函数的“外泄参数 / 返回来源参数”摘要,据此决定局部值留在栈上还是提升到保守式非移动 GC 堆。存储位置不会放宽移动与借用检查。

struct Foo {
    x: i32,
    y: i32,
}

fun make_ref() -> &Foo {
    let foo = Foo { x: 1, y: 2 };
    &foo // 引用越过栈帧,foo 被提升到 GC 堆
}
分析结果 MIR 分配
未逃逸且不需要稳定地址的局部 SSA 值,不生成分配指令
未逃逸但可变或被闭包按引用捕获 Alloca,栈上存储
逃逸的局部 HeapAlloc,GC 堆存储
未逃逸 / 逃逸的闭包环境 Alloca / HeapAlloc

GC 只负责地址,析构仍然确定

实现 `std::ops::Drop` 的类型会在所有者作用域结束时确定性地运行析构。逃逸到 GC 堆只改变值的地址,不改变析构时机;drop flag 保证移动之后不会重复析构。

struct FileHandle {
    raw: i32,
}

impl Drop for FileHandle {
    fun drop(&mut self) {
        // 释放 raw 对应的外部资源
    }
}

局部变量、参数、模式绑定、迭代元素、聚合字段和闭包环境都在析构覆盖范围内。

  • Drop + Copy、直接调用析构方法、从显式 Drop 类型移出字段都会被拒绝
  • for 循环的当前元素、迭代器和提前退出路径拥有各自独立的析构作用域

抽象不必付出运行时代价

trait 支持默认方法、关联类型与默认关联类型、父 trait 声明与传递 bound。泛型在 C 后端单态化,const generics 让长度直接进入类型。

trait Summary {
    fun title(&self) -> &str;

    fun summarize(&self) -> &str {
        self.title()
    }
}

struct Buffer<T, const N: usize> {
    data: [T; N],
}

impl 未覆写时使用 trait 默认方法体,显式覆写优先。

  • <T: A + B>where 子句;impl 上的 where 会检查 Paterson condition
  • 运算符通过 #[lang = "..."] trait 分派到用户类型;标量运算直接降为原生 C 运算,不生成包装函数
  • == / != 检查 PartialEq,有序比较检查 PartialOrd,支持异构右操作数 impl

漏掉的分支,编译器会告诉你在哪

`match` 对枚举、布尔值、`()`、整数、元组和结构体做递归穷尽性检查。非穷尽的整数匹配不是笼统报错,而是直接报告未覆盖的连续值区间。

fun classify(n: i32) -> i32 {
    match n {
        x if x < 0 => -1,
        0 => 0,
        _ => 1,
    }
}

穷尽性缺口以 E0039 报告,并列出未覆盖的取值区间。

  • guard 失败后继续检查后续 arm;带 guard 的 arm 不计入静态穷尽性
  • 支持枚举 unit / tuple / struct 变体模式,payload 绑定会进入 guard 和 arm 表达式

和 C 世界打交道,边界写在代码里

`unsafe extern "C"` 块内的导入默认不安全,可以用 `safe fun` 逐个声明确实安全的函数。C 后端不按函数名提供任何内置 helper,所有 `extern "C"` 声明都按普通外部符号生成。

unsafe extern "C" {
    safe fun abs(x: i32) -> i32;
    fun malloc(size: usize) -> *mut u8;
}

fun main() {
    let value = abs(-42);
    let pointer = unsafe { malloc(16) };
}

原始指针的解引用和索引需要处在 unsafe 上下文中。

  • C 导入中的 &str 映射为 const char*;带函数体的导出定义保留 { ptr, len } 胖指针
  • unsafe fununsafe fun(...) -> T 函数类型,支持单向安全函数转换

编译流程

从源码到 C,九个阶段

riddlec 已经能跑通完整前端和 C 后端。每个阶段都被仓库中的测试覆盖。

  1. 01

    词法与语法分析

    IncrementalParser 提供局部重解析 API

  2. 02

    AST 包装

    统一的语法树表示,属性随项一起进入 AST

  3. 03

    HIR 降级

    含 E0040 / E0050 / E0051 / E0052 诊断

  4. 04

    作用域图与名字解析

    基于片段的增量作用域图,支持部分失效

  5. 05

    类型检查

    可复用的 IncrementalTypeChecker

  6. 06

    逃逸分析

    过程间不动点,决定栈分配还是 GC 堆分配

  7. 07

    move checker

    移动后使用、借用冲突、借用期间的赋值与移动

  8. 08

    MIR 降级

    SSA 形式,Phi 节点、基本块、Alloca / HeapAlloc

  9. 09

    C 后端代码生成

    输出调用 rgc ABI 的 C11 代码

不指定后端时,riddlec 在完成 move / borrow 检查后停止,只有需要生成后端代码时才继续降级 MIR。

运行时

五个函数,组成整个运行时 ABI

每个 runtime provider 都要实现这五个符号:初始化栈、分配、调整和释放内存,以及触发回收。

rgc.h
void rgc_init(void *stack_bottom);
void *rgc_alloc(size_t size);
void *rgc_realloc(void *ptr, size_t size);
void rgc_free(void *ptr);
void rgc_collect(void);

crates/gc 提供默认的非移动、保守式 mark-sweep 实现;也可以在 Clue.toml[runtime].source 指向自己的 provider。

不依赖 Boehm GC
clue build 直接用系统 C 编译器编译生成的 C 与运行时源码,没有额外的第三方运行时依赖。
保守式、非移动
只有逃逸分析判定会越过当前栈帧的值才进入 GC 堆;其余值留在栈上,回收器不移动对象。
分配接口各司其职
rgc_reallocVector 扩容,rgc_free 释放 provider 管理的内存;没有 GC 的 provider 也可以忽略栈底并把 rgc_collect 实现为空操作。
完全可替换
运行时 provider 由 clue 选择,也接受自定义 provider——嵌入式或特殊场景可以自带分配器。

v0.2.3 新内容

更有表达力的语言与更宽的标准库

这一版加入括号 lambda、vec! 与 range 表达式、迭代器适配器、新的文件 / 时间 / 随机数 / 解析模块,LSP 也带来九个快速修复与 Clue.toml 支持。

  1. 01

    括号 lambda

    [v -> v * 2] 用短语法写匿名函数,参数与返回类型自动推断,values.map [v -> v * 2] 可以就地调用;它与 fun 闭包共享同一底层表示,vec![a, b]0..5 range 一并到位。

  2. 02

    更宽的标准库

    集合获得 removeinsertsort;迭代器新增惰性 map / filter / zip 与即时 count / fold / find;新的 std::fsstd::timestd::randomstd::parse 模块覆盖文件、休眠、随机数与数字解析。

  3. 03

    更敏锐的语言服务器

    九个由诊断驱动的快速修复、lambda 参数与方法链类型提示、Clue.toml schema 诊断与补全、整理导入、扩展选区、文档链接与拉取式诊断。

  4. 04

    编译器地基

    riddlec 支持多输入文件,常量在编译期求值用于数组长度与 const generics,切片以指针/长度对跨越 unsafe extern "C",GC 改用哈希对象注册表并自适应调整回收阈值。

工具链

一个管理器,四个二进制

ridup 管理 Riddle 的版本,一条工具链里是 clueriddlecriddleriddle-lsp。预编译版本可从 GitHub Releases 下载;从源码安装需要较新的 Rust stable。

选择并运行已安装的工具链,stablenightlycanary 三个通道各装一份,互不覆盖。它只管 Riddle 版本,C 编译器仍由选中的 clue 在本机寻找。

ridup toolchain install stable | nightly | canary

  • stablenightly 取 GitHub Release 归档,校验 SHA-256 后才替换旧工具链
  • canarymain 最新提交,在本机 cargo build --workspace --release,只需要 Rust 和 Cargo
  • ridup toolchain link dev <目录> 直接把本地构建目录当成一条工具链
  • riddle-toolchain.tomlRIDUP_TOOLCHAINclue +dev build 都能改变选择
  • 把 ridup 复制或硬链接成 clueriddlecriddleriddle-lsp 后,它会代理到选中的工具链
  • 下载与 Canary 构建都遵守 HTTPS_PROXY 等标准代理环境变量
在 GitHub 上看 ridup

检查 Riddle 源码并生成 C,前端检查与 C 后端都在同一个二进制里。

riddlec [--verbose] [--backend c] [--target <triple>] [--output <file>] <file>...

  • --backend c 生成调用 rgc ABI 的 C 代码
  • --target <triple> 选择目标平台,也可通过 RIDDLE_TARGETClue.toml 设置
  • 自动把 std/lib.rid 拼到用户源码后面
  • 不指定后端时只做前端检查,不降级 MIR

提供 Riddle 工具链的统一入口,当前包含源码格式化命令。

riddle fmt [--emit files|stdout|check] [--check] <file>...

  • 格式化文件或标准输入,也可以只检查而不写回
  • --tab-size 设置缩进宽度,--hard-tabs 使用制表符
  • 与 LSP 的 textDocument/formatting 复用同一 formatter

创建、检查、构建和运行 Riddle 项目。二进制项目会生成本机可执行文件,库项目可输出 .rlib / .rmeta 元数据以及静态库或动态库。

clue init | new | check | build | run [--target <triple>]

  • 设置 CC 时严格使用它,否则自动寻找可完成 C11 编译链接的系统编译器
  • --targetRIDDLE_TARGETClue.toml[build].target 依次覆盖宿主平台
  • ridup target add <triple> 安装目标 runtime;链接仍需要目标 C 工具链
  • path、git 和 registry 依赖统一解析进 lockfile,固定确切版本
  • clue build 会保留 .clue/build/<name>.c,方便检查生成结果
  • Clue.toml[runtime].source 可指向自定义运行时实现

基于 tower-lsp。解析错误、HIR 诊断、类型检查错误、move / escape 诊断全部通过 LSP 推送。

  • 项目范围补全,优先使用所有已打开文件的未保存内容
  • 语义 Token 区分自由函数、方法、struct、enum 和 trait
  • 错误码可跳转到错误码手册,注释与修复建议以 note: / help: 附加

编辑器支持

在你已经在用的编辑器里

仓库的 editors 目录提供开箱即用的适配配置。

  • Helix
  • VS Code
  • Zed
  • IntelliJ IDEA 2026.1+

现在就能用

  • .rid 文件识别
  • Clue 项目、未保存文件和未打开模块的诊断
  • 解析、类型、move / borrow 诊断
  • 函数、方法、struct、enum、trait、参数和可变绑定的语义高亮
  • 跨模块返回类型的局部变量 Inlay Hint
  • 跨文件补全,含字段、实例方法、枚举变体和关联函数
  • 可变闭包绑定 Code Action
  • 增量文档同步与 Semantic Token delta
  • Hover、跳转定义与实现、查找引用、重命名与格式化
  • 工作区索引与自动导入

仍在完善

  • 更丰富的语义重构代码操作

开始使用

五条命令,跑起第一个程序

ridup 装工具链并在通道之间切换,也可以从 GitHub Releases 下载压缩包加入 PATH

  1. 1

    安装工具链

    装完通道要再选一次默认工具链;之后 ridup show 会告诉你当前用的是哪条,以及为什么是它。

    cargo install --git https://github.com/riddle-lang/ridup
    ridup toolchain install stable
    ridup default stable
  2. 2

    创建并运行项目

    clue run 会先完成与 clue build 相同的构建,再运行生成的可执行文件。

    clue new hello
    cd hello
    clue check
    clue build
    clue run

clue build 会保留 .clue/build/hello.c。设置 CC 时 Clue 严格使用它;否则自动寻找 ccgccclang 及其版本化命令,Windows 还支持 clang-clcl。想直接敲 clueriddle,把 ridup 复制或硬链接成对应名称即可;不做代理时用 ridup run stable clue new hello

项目状态

这是技术预览,我们不藏着

v0.2.3 的语言和工具链仍可能发生不兼容变化。下面是当前真实的能力边界。

已经可用

  • 类型检查、move checker、借用与逃逸分析
  • 泛型、const generics、trait 与关联类型
  • 闭包与 Fn / FnMut / FnOnce 调用能力检查
  • 递归穷尽性检查的 match
  • IntoIterator / Iterator 驱动的 for
  • unsafe 语义与 C FFI
  • 数组、切片、字符串与 Vector 标准库
  • Drop、运算符重载与确定性 C backend
  • 元组类型、tuple 模式与 tuple 枚举变体
  • 过程宏与标准 derive(含 Default、Hash、Ord 等)
  • 支持位置、命名与 debug 占位符的格式化输出
  • 括号 lambda [v -> v * 2]fun 闭包并存
  • 迭代器适配器、集合删除与 vec!
  • 标准库的文件、时间、随机数与解析模块
  • riddle fmt 源码格式化 CLI,与 LSP 共享 formatter
  • 七个目标 triple 的交叉构建与 target runtime
  • 内置标准库、C11 代码生成、项目工具与 LSP

当前限制

  • 格式化已支持位置、命名与 :? 占位符;宽度、对齐和填充说明符尚未实现
  • 浮点余数尚未支持,Rem / RemAssign 目前只为整数实现
  • 泛型偏向单态化,尚未覆盖完整 Rust 泛型能力
  • 只有 C 后端;生成可执行文件依赖系统 C 编译器
  • 逃逸分析粒度是整个局部变量,不做字段级拆分
  • 闭包按整个绑定捕获,不做字段级精确捕获
  • 没有裸函数指针类型;可调用值需用 impl Fn(...) -> Tdyn Fn(...) 声明

不保证语法和 ABI 稳定。请把 Riddle 当作可以认真试用、但还不能上生产的语言。

发展规划

接下来要做什么

这份路线图就是项目当前真实的优先级排序:正确性最先,语言能力其次,工具链与生态随后。顺序会随反馈调整,不构成时间承诺。

01 · 近期

语言核心

正确性优先,先把安全语义的地基打完。

  • 带标签的 break / continue,以及 or / range / slice 模式
02 · 中期

标准库与工具链

语言能写的东西多了,库和工具要跟上。

  • 标准库扩充:缓冲 IO、完整格式说明符与更强的解析能力
03 · 远期

核心稳定之后

这些方向明确排在核心语义稳定之后,现在不抢跑。

  • 并发与 async / await
  • C 之外的其他后端
  • 语法与运行时 ABI 的稳定化承诺

想影响优先级?到 GitHub Issues 告诉我们你最需要哪一项。

关于作者

一个人,一门语言

zi2ven

zi2ven

Riddle 的作者与唯一维护者

Riddle is Best

riddlec 的第一行代码到你正在看的这个页面——编译器、clueriddle-lspridup、标准库、文档和 Playground——都出自同一双手。

也因为是一个人写的,这个项目更相信把边界写清楚:哪些已经能用、哪些还不行,官网和文档都照实标注。试用中发现问题,欢迎直接到 GitHub 提 issue。

在 GitHub 上找到 zi2ven

写几行 Riddle 试试

不用安装任何东西,在浏览器里就能编译运行。