Cyrus 编程语言
Cyrus 旨在提供:
- 对机器的显式控制
- 简洁、人类可读的语法
- 极少的抽象
- 高性能执行
- 精确的低层交互
- 严格的命令式/过程式范式
Cyrus 诞生于对现代语言中日益蔓延的复杂性的不满。虽然 Rust 和 Go 等生态系统提供了令人惊叹的特性,但它们也引入了陡峭的学习曲线、沉重的运行时或许多系统程序员根本不需要或不想使用的限制性抽象。
Cyrus 做出了不同的押注:简洁、显式和优雅可以共存,而不会牺牲一丝一毫的性能。
这门语言专为以下人群打造:
- 热爱 C 语言但希望在没有垃圾回收的情况下获得现代演进的开发者。
- 对 Rust 的借用检查器感到疲惫,但仍希望拥有现代、高性能基础设施的开发者。
- 需要零隐藏运行时开销和绝对内存控制的嵌入式和操作系统级工程师。
- 任何寻找一种美丽、富有表现力、尊重你智慧和经验的低层语言的人。
哲学:开发者掌控
Cyrus 遵循一个单一、毫不妥协的原则:开发者是掌控者。
该语言不会试图比你更聪明、隐藏硬件,或用自动规则包裹你的决策。我们不会试图通过重量级的编译器强制安全系统来消除每一个 bug,也不会引入大规模的范式转变。
相反,Cyrus 采用系统编程中经过验证的概念并对其进行精炼。每一次分配、变更、内存间接引用和分发策略都在你的代码中显式声明。当你阅读一个 Cyrus 文件时,你确切地知道机器正在做什么。
显式语义
Cyrus 要求每一步都有明确的意图。没有任何事情是隐式、悄悄或幕后发生的。
可见的可变性
无需向后扫描来判断变量是否发生变化。第一个标记就告诉你一切:
const max_retries = 5; // immutable, forever
var current_try = 0; // mutable
无静默截断
C 语言喜欢静默地进行类型转换,丢弃位并掩盖缺陷。Cyrus 拒绝猜测。安全的拓宽(例如从 int32 到 int64)会自动发生,但如果存在任何数据丢失或符号不匹配的风险,你必须使用 @cast 显式授权:
const a: int32 = 300;
const b: int64 = a; // OK: Safe widening
const c: int32 = @cast(int32, b); // OK: Explicit cast required
可追踪指针
在 Cyrus 中,只需一瞥即可判断是否正在跨越内存边界。我们使用 . 表示直接访问,使用 -> 表示指针间接访问:
var box = Box { value: 1 };
var box2 = box.methodA(5)->methodB(50);
var box3 = box2->fieldA;
// The '->' tells you exactly where the pointer dereference happens
手动、可预测的内存
没有垃圾回收器。你分配内存,然后释放它。defer 关键字确保清理操作在作用域退出时精确执行(按 LIFO 顺序),使资源管理保持扁平且易读:
import std::mem::libc{LibcAllocator};
import std::mem{Allocator};
pub fn main() {
const allocator = LibcAllocator.new();
var buffer: int32* = allocator.alloc(64 * @sizeof(int32));
defer allocator.free(buffer);
buffer[0] = 10;
}
完全机器控制
Cyrus 信任你知道自己在做什么。它提供了用于映射内存和与硬件通信的底层工具,并期望你能承担相应的责任。
- 自带分配器: 自定义分配器(栈、bump、libc)是使用
Allocator接口的一等公民。 - 类型双关: 使用
union在不进行类型转换的情况下重新解释原始内存布局。 - 指针算术: 完全支持基于 GEP 的索引和指针运算(
ptr + 5)。 - 裸机就绪: 内联汇编、用于无缝 C ABI 兼容性的
extern,以及用于直接编写中断处理程序的naked函数。
安全性
控制是有代价的。我们不会对你的代码进行过度保护;data races、dangling pointers 和 use after free 错误仍然可能发生。这不是缺陷,而是为了实现对机器的直接、无过滤访问而做出的刻意权衡。
运行时强制实施 bounds checking、null pointer access 和 integer overflow。为了更深入的调试,我们支持 ASan 和 TSan 等 sanitizer。
变量默认是 zero initialized 的。若要退出此行为,请使用 undefined 关键字来禁用零初始化。
语言概览
以下是 Cyrus 的一瞥。关于学习该语言的完整且最新的详细信息,请查阅我们的 Documentation,它随着项目的进展而不断演变。
结构体
结构体是用户定义的复合类型,用于将相关的数据组合在一起。它们直接映射到内存,没有隐藏的对象头或 vtable。
struct User {
pub name: uint8*,
pub age: uint32,
id: uint32 // private by default
}
方法使用显式接收者定义在结构体内部:
struct Counter {
pub value: int32,
pub fn increment(self: Self*) {
self->value += 1;
}
pub fn reset(var self: Self) {
self.value = 0; // operates on a local copy
}
}
Cyrus 还支持用于内联使用或快速配置的匿名未命名结构体。如果字段名称和类型完全匹配,命名结构体和未命名结构体是完全兼容的:
struct Point {
pub x: int32,
pub y: int32
}
pub fn main() {
const pt: Point = struct { x: 10, y: 20 }; // OK
}
枚举
枚举是代数求和类型,其中每个变体可以携带自己的载荷。Cyrus 支持多种变体类型:单元(无载荷)、元组(位置字段)、结构体(命名字段)和值(常量)。
enum Message {
Quit, // Unit variant
Move(int32, int32), // Tuple variant
Write { text: const uint8* }, // Struct variant
Status = 200 // Valued variant
}
你可以使用 switch 语句对枚举进行匹配,只要处理或返回其余部分,就允许部分匹配:
pub fn handle(msg: Message) {
switch (msg) {
case .Quit => {
printf("Quitting\n");
}
case .Move(x, y) => {
printf("Move to (%d, %d)\n", x, y);
}
case .Write { text } => {
printf("Message: %s\n", text);
}
case .Status(code) => {
printf("Status code: %d\n", code);
}
}
}
联合体
联合体在所有字段之间共享相同的内存地址。由于它们完全不受检查,因此非常适合用于内存高效的 C 互操作或需要直接控制原始数据的底层字节操作。
union IntBytes {
value: int32,
bytes: uint8[4]
}
pub fn main() {
const data = IntBytes { value: 0x12345678 };
}
泛型
泛型支持具有完整编译时安全性的类型参数化代码。只有命名类型和函数可以是泛型的,未命名结构体和匿名函数被排除在外。
泛型定义在编译时进行类型检查,无需实例化。编译器会预先验证逻辑,尽早捕获错误,并最大限度地减少大量模板化代码的编译开销。
泛型函数
在尖括号中添加类型参数 <T> 以使函数成为泛型:
fn identity_pair<T>(values: T[2]) T[2] {
return values;
}
pub fn main() {
const p1 = identity_pair({1, 2}); // Inferred
const p2 = identity_pair<int32>(int32[2] {3, 4}); // Explicit
printf("%d %d\n", p1[0], p2[0]);
}
通用函数支持递归:
fn count_to_five<T>(x: T) {
printf("%d ", x);
if (x < 5) {
count_to_five(x + 1);
}
}
泛型
结构体、联合体和枚举都可以接受类型参数。使用 _ 让编译器推断某个类型参数,同时手动指定其他参数:
struct Pair<K, V> {
pub key: K,
pub value: V,
}
pub fn main() {
const entry = Pair<uint32, _> {
key: 1,
value: "Cyrus"
};
}
通用枚举在可选值和结果方面表现出色:
enum Option<T> {
Some(T),
None
}
fn print_opt(opt: Option<int32>) {
switch (opt) {
case .Some(val) => printf("Value: %d\n", val);
case .None => printf("Nothing\n");
}
}
泛型方法
方法可以引入自己的类型参数,即使结构体本身不是泛型的:
struct Math {
pub fn add<T>(x: T, y: T) T {
return x + y;
}
}
struct Box<T> {
value: T,
pub fn new(val: T) Self {
return Self { value: val };
}
}
默认类型参数
当类型推断失败时,提供回退类型:
struct Result<V, E = uint64> {
pub value: V,
pub error_code: E,
}
pub fn main() {
var res = Result<uint8*, _> { value: "Success", error_code: 0 };
}
泛型类型别名
为复杂的泛型类型创建简写,或使别名本身成为泛型:
struct Pair<K, V> {
key: K,
value: V,
}
type IntPair = Pair<int32, int32>; // Non-generic alias
type Handler<T> = fn(T) void; // Generic alias
pub fn main() {
const log: Handler<int32> = fn(x: int32) void {
printf("%d", x);
};
}
通用接口
接口可以定义通用契约。一个对象可以实现某个具体实例化,或者本身是通用的:
interface IValidator<T> {
fn validate(&const self, value: T) bool;
}
struct AgeValidator : IValidator<int32> {
pub fn validate(&const self, val: int32) bool {
return val >= 18;
}
}
interface Shape<T> {
fn area(&const self) T;
}
struct Rectangle<T> : Shape<T> {
width: T,
height: T,
pub fn area(&const self) T {
return self->width * self->height;
}
}
分离类型参数
类型参数属于类型构造器或方法调用:
struct InfixCalc<T> {
pub x: T,
pub y: T,
pub fn new(x: T, y: T) Self {
return Self { x, y };
}
pub fn add_to_x<V>(self: Self*, value: V) {
self->x += @cast(T, value);
}
pub fn static_sum<K>(a: T, b: K) T {
return a + @cast(T, b);
}
}
pub fn main() {
var calc = InfixCalc.new(5, 7);
calc.add_to_x<uint32>(1); // Method type arg only
calc.add_to_x<uint64>(2);
// Static call: provide both type and method args
const result = InfixCalc<int64>.static_sum<uint32>(10, 20);
const inferred = InfixCalc.static_sum(10, 20);
printf("%d %d\n", calc.x, result);
}
多态性
Cyrus 使用两种不同的多态策略:针对静态泛型的调用点单态化,以及针对运行时接口对象(胖指针)的创建点动态分派。
默认情况下,多态性是静态的。当你需要运行时多态性和虚函数表时,必须显式使用 dynamic 关键字:
interface Speaker {
fn speak(&const self);
}
struct Dog : Speaker {
pub fn speak(&const self) {
printf("Woof!\n");
}
}
pub fn main() {
// Explicit dynamic dispatch using the 'dynamic' keyword
const speaker: Speaker = dynamic Dog{};
speaker.speak();
}
内存管理
分配器以标准接口的形式实现,允许你在系统分配器、内存池(arenas)和自定义策略之间无缝切换。
import std::mem::arena{ArenaAllocator};
import std::mem::libc{LibcAllocator};
import std::mem{Allocator};
import std::libc{printf};
pub fn main() {
const allocator: Allocator = dynamic LibcAllocator.new();
var arena = ArenaAllocator.new(allocator, 16);
defer arena.destroy();
var sum: int32 = 0;
for (var i = 0; i <= 50; i += 1) {
var x: int32* = arena.alloc(@sizeof(int32));
@assert(x, "alloc failed");
*x = i;
sum += *x;
if (i == 20) {
arena.reset();
}
}
}
ABI / C 互操作
Cyrus 通过 extern(c) ABI 规范提供无缝的双向 C 互操作。这允许你从 Cyrus 调用 C 函数,并将 Cyrus 函数暴露给 C 代码。
调用 C 函数很简单:
import std::libc{printf, malloc, free};
pub fn main() {
const msg: const uint8* = "Hello from Cyrus!";
printf("%s\n", msg);
var buffer = malloc(64);
defer free(buffer);
}
将 Cyrus 函数导出以供 C 语言使用同样简单:
extern(c) fn add(a: int32, b: int32) int32 {
return a + b;
}
extern(c) fn greet(name: const uint8*) {
printf("Hello, %s!\n", name);
}
未来之路:translate-c
我们正在积极研究 translate-c,这是一款能够将 C 头文件和源文件自动转换为惯用 Cyrus 代码的工具。这将使增量迁移现有 C 项目变得更加容易,而无需进行完全重写。
translate-c 工具旨在处理:
- 函数声明和定义
- 结构体、联合体和枚举定义
- 类型定义和宏(在可能的情况下)
- 预处理器指令
- 平台特定的代码路径
ABI 稳定性
Cyrus 不 保证稳定的 ABI。我们保留在版本之间更改调用约定、类型布局和优化策略的权利,以提升性能和代码质量。
因此,我们强烈建议:
- 在所有跨语言边界使用 C ABI(
extern(c)) - 避免在互操作代码中对内部 Cyrus 类型布局做出假设
- 将 Cyrus 到 Cyrus 的 ABI 视为可能演变的实现细节
我们认为这种权衡是值得的——优先考虑长期的优化和清晰的设计,而非短期的 ABI 稳定性。如果你需要稳定的接口,请使用 C 作为通用标准。
Cyrus 拒绝做什么
Cyrus 建立在刻意省略复杂性的哲学之上。我们主动排除大量功能,以保持语言的可预测性、透明性,并完全消除隐藏的魔法。
| 省略项 | 省略原因 |
|---|---|
| 垃圾回收 | GC 隐藏了分配成本并引入了不可预测的暂停时间。 |
| 借用检查器 | 复杂的生命周期规则决定了你的代码编写方式。我们更倾向于开发者的自由度。 |
| 类继承 | 深层层级结构会掩盖数据流。Cyrus 偏好简单的组合。 |
| 全局猴子补丁 | 无法在定义模块之外向 struct 添加方法。 |
| 隐藏分配 | 每次堆分配都需要你显式调用分配器。 |
| 函数式范式 | 高阶魔法、隐藏闭包和持久化数据结构会掩盖执行流程。Cyrus 严格保持过程式风格。 |
Cyrus 的定位
每种语言都是一种妥协。Cyrus 针对可读性、机械亲和性以及开发者控制权进行了优化。
- 如果 C 让你感到沮丧,因为它缺乏真正的模块系统、泛型和严格的类型安全,Cyrus 为你提供 C 的控制力与现代语言的人体工程学。
- 如果 Rust 感觉笨重,且你发现自己为了简单数据结构而与借用检查器作斗争,Cyrus 为你提供现代工具链,而不受生命周期限制。
- 如果 Go 过于抽象,带有其垃圾回收器和运行时,Cyrus 让你回归底层硬件。
安装
Cyrus 编译器目前支持 Linux (x86_64)。对额外平台的支持将在自托管之后跟进。
安装 Nightly
从我们的 GitHub Actions 工件 下载适用于 Linux 的最新 nightly 二进制文件。
从源码构建
有关从源码构建编译器的说明,请参阅 从源码构建指南。
当前状态与路线图
Cyrus 作为语言和编译器正处于重度开发阶段。我们目前专注于编译器的自托管,这是一个重大里程碑,将解锁工具链的显著改进。
即时优先级
- 自托管: Cyrus 编译器正在用 Cyrus 本身重写。这将验证语言设计,提高编译器性能,并实现更快的迭代。
自托管后路线图
一旦自托管完成,我们将专注于:
- Slices: 安全的、带边界检查的数组视图;实现将遵循自托管。
- Generic Instantiation Safety: 当前行为类似于 C++ 模板(在实例化时检查)。在增强分析器后,泛型将在编译时进行完全的类型检查,无需实例化,类似于 Rust 的方法。
- Attributes: 将在自托管后引入,以减少语言中的修饰符关键字数量。
- Test Framework: 内部测试框架和全面的测试套件将在自托管后构建。
- Matrix Type: 支持 SIMD 的多维数组支持(类似于向量类型)将在自托管后添加。
- Variadic Arguments: 类型安全的可变参数函数参数将在自托管后实现。
长期研究
我们也在评估几个主要系统,花时间确保它们与我们显式控制的哲学保持一致:
- Concurrency: 正在评估纤程、协程和操作系统级原语。
- Error Handling: 正在评估显式的、无样板代码的机制,以避免隐式展开或运行时异常。
- Compile-Time Execution, Reflection, and Metaprogramming: 正在探索受控的、可预测的代码生成和类型检查机制,而不引入沉重的运行时开销。
Cyrus 正处于重度开发阶段,尚未准备好用于生产环境。随着核心基础设施的成熟,语法、语义和编译器行为将会发生变化。
Cyrus 并不试图成为世界上最安全的语言。 它试图成为你使用过的最透明、可控且可维护的系统语言。
行为准则
我们致力于为所有参与或接触 Cyrus 的贡献者营造一个开放、友好且包容的社区。
请查阅我们的官方 行为准则 以了解关于预期行为、社区标准和报告程序的详细指南。
贡献
我们欢迎各种形式的贡献,无论是报告 bug、改进文档,还是参与编译器本身的开发。
要开始贡献,请查阅我们的 贡献指南 以获取有关设置开发环境、拉取请求工作流程和编码标准的说明。
许可证
Cyrus 是根据 MIT 许可证 分发的开源软件。您可以自由地在许可证条款下使用、修改和分发此软件。