اهداف طراحی
کوروش یک زبان برنامهنویسی سیستمی پُرکارایی با سینتکس و سمانتیکی صریح، انتزاع و زمان اجرایی اندک، و مدیریت حافظهٔ دستی است.
فلسفه
کوروش بر اساس یک اصل سختگیرانه بنا شده است:
توسعهدهنده مسئول است. زبان باید هر نقطه کنترل را قابل مشاهده کند، نه اینکه تصمیمات را پشت قوانین خودکار پنهان کند.
این زبان سعی نمیکند همه باگها را از طریق سیستمهای ایمنی اعمالشده توسط کامپایلر حذف کند، و نه پارادایمهای جدید یا لایههای انتزاع سنگین معرفی میکند. در عوض، مفاهیم اثباتشده از برنامهنویسی سیستمی را انتخاب کرده و آنها را به شکلی سازگار و صریح بهبود میبخشد، جایی که هر تصمیم درباره تغییرپذیری (mutability)، تخصیص حافظه، غیرمستقیمسازی (indirection) و استراتژی توزیع (dispatch) در کد اعلام میشود، نه اینکه استنباط گردد.
کوروش بر همان مدل ماشینی C بنا شده است، بهگونهای طراحی شده که هر اتفاقی در سطح سختافزار از خواندن کد آشکار باشد.
معناشناسی صریح
کوروش با الزام توسعهدهندگان به بیان مستقیم قصد خود، کنترل را قابل مشاهده میکند و هیچ ابهامی درباره عملکرد کد باقی نمیگذارد.
تغییرپذیری صریح است
هر متغیر تغییرپذیری خود را در نقطه تعریف اعلام میکند:
const max_retries = 5; // immutable
var current_try = 0; // mutable
یک binding از نوع const هرگز نمیتواند دوباره مقداردهی شود — کامپایلر این را اعمال میکند. یک binding از نوع var به خواننده نشان میدهد که این مقدار در طول عمر خود تغییر میکند. نیازی به جستجوی عقبگرد برای بررسی اینکه آیا یک نام با let یا let mut اعلام شده است نیست؛ نشانه در اولین token هر اعلان قرار دارد.
تبدیل نوع قابل مشاهده است
در جایی که C تبدیلهای ضمنی خاموشی انجام میدهد که میتوانند اطلاعات را از دست بدهند، کوروش نیاز به cast صریح دارد:
const a: int32 = @cast(int32, 0xFFFFFFFF);
const b: int16 = @cast(int16, 0xFFFFFFFF);
تبدیلهای گسترشی (مثلاً int32 به int64 با علامت یکسان) بهطور ضمنی مجاز هستند زیرا همیشه ایمن هستند. هر Narrowing یا عدم تطابق علامت نیاز به یک @cast قابل مشاهده دارد. این کار یک دسته کامل از باگهایی را که در آن C بهطور خاموش مقادیر را کوتاه یا بازتفسیر میکند، حذف مینماید.
غیرمستقیمسازی اشارهگر قابل پیگیری است
کوروش بین دسترسی مستقیم (.) و دسترسی از طریق اشارهگر (->) تمایز قائل میشود و هر dereference را در محل فراخوانی قابل مشاهده میکند:
var box = Box { value: 1 };
var box2 = box.methodA(5)->methodB(50); // thin arrow means we're going through a pointer
عملگر -> به خواننده میگوید «اینجا غیرمستقیمسازی رخ میدهد» بدون اینکه نیاز باشد نوع سمت چپ را بهصورت ذهنی تشخیص دهد.
مدیریت حافظه دستی و قابل مشاهده است
هر تخصیص و آزادسازی حافظه در کد صریح است:
import std::mem{Allocator, LibcAllocator};
pub fn main() {
const allocator = LibcAllocator.new();
var buffer: int* = allocator.alloc(64 * @sizeof(int));
defer allocator.free(buffer); // cleanup is visible and scoped
buffer[0] = 369;
}
کلیدواژه defer تضمین میکند که پاکسازی در هنگام خروج از scope اجرا میشود — به ترتیب معکوس اعلان (LIFO) — و طول عمر منابع را قطعی و قابل مشاهده میکند بدون نیاز به تو در تو کردن (nesting):
pub fn main() {
defer log(100); // runs last at function exit
{
defer log(10 + 1); // runs when inner scope exits
defer log(10 + 2);
log(2);
}
}
تبدیل اعداد صحیح سختگیرانه است
کوروش صحت اعداد صحیح را بهطور سختگیرانه اعمال میکند تا باگهای کوتاهشدن خاموش و عدم تطابق علامت که کدهای C را آزار میدهد حذف کند:
- عدم وجود تبدیل Narrowing ضمنی — یک مقدار
int64نمیتواند بهطور خاموش بهint32تبدیل شود - علامت باید مطابقت داشته باشد — ترکیاعداد علامتدار و بدون علامت نیاز به
@castصریح دارد - گسترش ایمن خودکار است —
int32بهint64با علامت یکسان بهطور ضمنی تبدیل میشود - عدم تطابق علامت حتی در گسترش نیاز به
@castصریح دارد
const a: int32 = 300;
const b: int64 = a; // OK: safe widening, matching signedness
const c: int32 = @cast(int32, b); // explicit: narrowing requires cast
const u: uint32 = 10;
const i: int64 = @cast(int64, u); // explicit: signedness mismatch requires cast
این کار از باگهای کلاسیک C جلوگیری میکند که در آنها:
- یک شمارنده حلقه بهطور خاموش میپیچد زیرا در نوع کوچکتری ذخیره شده است
- یک مقایسه بین علامتدار و بدون علامت نتایج غیرمنتظره ایجاد میکند
- اندازه فایل ۶۴ بیتی هنگام انتساب به یک متغیر ۳۲ بیتی کوتاه میشود
آنچه میتوانید انجام دهید
کوروش به شما اعتماد دارد که سیستمی را که با آن کار میکنید درک میکنید. به جای محدود کردن آنچه میتوانید بیان کنید، ابزارهایی را در اختیار شما قرار میدهد تا ماشین را مستقیماً کنترل کنید — و مسئولیت عواقب آن را بر عهده میگیرد.
تخصیصدهنده حافظه خود را بنویسید — تخصیصدهندههای سفارشی شهروندان درجه یک از طریق واسط Allocator هستند، با پیادهسازیهای stack، bump و libc-backed که در کتابخانه استاندارد ارائه شدهاند:
pub struct BumpAllocator : Allocator {
ptr: void*,
cap: usize,
offset: usize,
pub fn alloc(&self, size: usize) void* {
const current = self->offset;
const next = current + size;
if (next > self->cap) return null;
self->offset = next;
return @cast(void*, @cast(uintptr, self->ptr) + current);
}
pub fn free(&self, ptr: void*) void {
// no-op: bump allocators don't free individually
}
}
حافظه خام را به انواع مختلف تفسیر کنید — unionها امکان type punning بدون cast را فراهم میکنند:
union IntBytes {
value: int;
bytes: uint8[4];
}
var data = IntBytes{ value: 0x12345678 };
// data.bytes[0] == 0x78 on little-endian,
// same memory, different type
طرح، تراز و padding را کنترل کنید — structهای فشرده (packed)، تراز فیلد صریح و ویژگیهای ABI برای نگاشت دقیق حافظه.
از حساب اشارهگرها برای مسیرهای بحرانی از نظر عملکرد استفاده کنید — کوروش بهطور کامل از نمایهسازی مبتنی بر GEP و تفاوت اشارهگرها پشتیبانی میکند:
const arr: int32[10];
const ptr: int32* = &arr[0];
var ptr2: int32* = ptr + 5; // pointer arithmetic
*ptr2 = 99;
مستقیماً با سختافزار، هسته یا اهداف bare-metal ارتباط برقرار کنید — اسمبلی درونخطی، extern برای ABI زبان C، توابع naked و قراردادهای فراخوانی سفارشی همگی در دسترس هستند:
extern fn printf(fmt: const char*, ...) int32;
naked fn interrupt_handler() void { /* no prologue/epilogue */ }
بهای کنترل
از آنجایی که کوروش یک لایه ایمنی بین شما و ماشین قرار نمیدهد، موارد زیر همچنان ممکن هستند:
- خواندن یا نوشتن حافظه نامعتبر
- استفاده پس از آزادسازی (use-after-free) و آزادسازی مضاعف (double free)
- اشارهگرهای آویزان (dangling pointers)
- شرایط مسابقه (data races)
اینها بهای کنترل هستند — نتیجه دسترسی مستقیم به ماشین، نه شکست زبان. ابزارهای sanitizer زمان اجرا در طول توسعه برای کمک به شناسایی این مشکلات در دسترس هستند.
زبانی که از هر خطای حافظه جلوگیری کند باید آنچه را که میتوانید بیان کنید محدود کند. کوروش معامله معکوس را انتخاب میکند.
مدل داده
برنامههای کوروش از سه ساختار داده اصلی ساخته شدهاند — هر یک برای هدف خاصی انتخاب شده، بدون مدل شیء پنهان یا سلسلهمراتب کلاس.
Struct
فیلدهای نامگذاری شده، متدها و دید صریح:
struct User {
pub name: char*;
pub age: uint;
role: char*; // private field
}
pub fn main() {
var user = User { name: "Cyrus", age: 2500, role: "founder" };
user.name; // OK: public
// user.role; // ERROR: private outside the struct module
}
کوروش همچنین از structهای بینام (anonymous) برای گروهبندیهای یکبار مصرف و سازگاری ساختاری بین structهای نامدار و بینام با طرحهای فیلد یکسان پشتیبانی میکند:
var point = struct { x: 10, y: 20 }; // unnamed struct, type inferred
pub const config = struct { // unnamed struct
host = "127.0.0.1",
port = 8080
};
pub fn main() {
point.x += 5;
const temp: struct { a: int, b: float64 } = obj; // compatible with named struct
}
Enum
Enumها در کوروش انواع جمع جبری واقعی (algebraic sum types) هستند — هر variant میتواند بار (payload) خود را حمل کند و کامپایلر تطابق جامع (exhaustive matching) را اعمال میکند. چهار شکل variant پشتیبانی میشود:
Variantهای واحد (Unit) — برچسبهای ساده بدون داده:
enum ConnectionState {
Disconnected,
Connected,
Failed
}
Variantهای تاپلی (Tuple) — بار موقعیتی:
enum Task {
Delay(uint32),
Timeout(uint32, const char*)
}
Variantهای ساختاری (Struct) — بار با فیلدهای نامگذاری شده:
enum Error {
NotFound { id: uint32, msg: const char* },
PermissionDenied { resource: const char* }
}
Variantهای مقداری (scalar) — ارتباط با مقدار ثابت:
enum HttpStatus {
OK = 200,
NotFound = 404,
InternalError = 500
}
تطابق جامع است — کامپایلر بررسی میکند که همه variantها مدیریت شدهاند:
switch (err) {
case .NotFound { id, msg } => printf("%u: %s", id, msg);
case .PermissionDenied { resource } => printf("denied: %s", resource);
}
// compiler error if any variant is missing
این کار جایگزین الگوی C شامل enum + union بدون برچسب + ردیابی دستی discriminant با یک ساختار واحد میشود که در آن کامپایلر برچسب را مدیریت کرده و صحت را اعمال میکند.
enumهای بینام را میتوان بدون نوع نامدار بهصورت درونخطی در محل استفاده اعلام کرد:
var mode: enum { Off = 0, On = 1 } = .On;
switch (mode) {
case .On(value) => printf("on: %d", value);
case .Off(value) => printf("off: %d", value);
}
Union
Unionها type punning بدون بررسی را فراهم میکنند — همه فیلدها آدرس حافظه یکسانی را به اشتراک میگذارند و خواندن فیلدی که به تازگی نوشته نشده است، رفتار تعریفنشده (undefined behavior) محسوب میشود:
union Payload {
i: int64;
s: char*;
}
var data = Payload { s: "Cyrus" };
// data.i is gone, writing to s now overwrites stale memory
var iptr: int64* = &data.i; // pointer aliasing: same address as data.s
*iptr = 2500;
زمانی از union استفاده کنید که به نمایش داده کارآمد از نظر حافظه یا قابلیت همکاری با C نیاز دارید. برای unionهای دارای برچسب ایمن، به جای آن از enum استفاده کنید.
unionهای بینام را میتوان بهصورت درونخطی برای طرح موقت یا مقداردهی انواع union نامدار استفاده کرد:
union Payload {
i: int64,
s: char*
}
pub fn main() {
const layout: Payload = union { s: "Cyrus!" };
printf("%s\n", layout.s);
}
جنریکها
structها، enumها، unionها و توابع همه میتوانند با نوع پارامتریزه شوند. جنریکها در زمان کامپایل با هزینه زمان اجرای صفر مونومورفایز میشوند (monomorphized) — کامپایلر یک کپی تخصصی جداگانه برای هر ترکیب نوع مشخص تولید میکند:
struct Triple<A, B, C> {
pub first: A,
pub second: B,
pub third: C,
pub fn new(a: A, b: B, c: C) Self {
return Self { first: a, second: b, third: c };
}
}
pub fn main() {
const triple = Triple.new(3, 4.5, "hello"); // implicit var type
const triple2: Triple<int, float64, char*> = Triple.new(3, 4.5, "hello"); // explicit var type
printf("%d %f %s\n", triple.first, triple.second, triple.third);
}
چندریختی کنترلشده
کوروش دو استراتژی توزیع (dispatch) ارائه میدهد که هر یک بهطور صریح در محل استفاده انتخاب میشوند:
توزیع ایستا (Static dispatch) — مونومورفایزیشن تضمینی، بدون غیرمستقیمسازی:
fn process<T: Speaker>(animal: T) {
printf("%s\n", animal.speak()); // direct call, no vtable
}
توزیع پویا (Dynamic dispatch) — اشارهگر چاق (fat pointer) با vtable، که بهطور صریح از طریق dynamic ساخته میشود:
const speakers = Speaker[2]{dynamic dog, dynamic cat};
printf("%s %s\n", speakers[0].speak(), speakers[1].speak());
کلیدواژه dynamic ساخت vtable را در محل ایجاد قابل مشاهده میکند. خواننده میتواند دقیقاً ببیند که چندریختی زمان اجرا از کجا وارد سیستم میشود.
متدها و کپسولهسازی
متدها توابعی هستند که در بدنه struct تعریف میشوند. آنها قوانین سازگاری بدون وراثت یا توزیع مجازی پیشفرض را دنبال میکنند.
متدهای نمونه
متدها نمونه struct را از طریق یک پارامتر گیرنده صریح دریافت میکنند:
struct SimpleCounter {
pub count: int;
pub fn new(initial: int) Self {
return Self{ count: initial };
}
pub fn increment(&self) { // mutable reference receiver
self->count++;
}
pub fn describe(&const self) { // const reference receiver
printf("count: %d\n", self->count);
}
pub fn into_value(self) int { // by-value (consuming) receiver
return self.count; // self is a copy
}
}
سه شکل گیرنده:
| شکل | معناشناسی | میتواند تغییر دهد؟ |
|---|---|---|
&self | اشارهگر تغییرپذیر به self | بله |
&const self | اشارهگر تغییرناپذیر به self | خیر |
self | کپی بهوسیله مقدار | بله (فقط کپی محلی) |
فراخوانی متد / قوانین دسترسی به اعضا
متدها با استفاده از نقطه (dot syntax) روی مقادیر یا -> روی اشارهگرها فراخوانی میشوند:
var c = SimpleCounter.new(10);
c.increment(); // value receiver
var ptr = &c;
ptr->increment(); // pointer receiver
متدهای ایستا
متدهای بدون پارامتر گیرنده روی خود نوع فراخوانی میشوند:
pub struct MathUtils {
pub fn square(x: int) int {
return x * x;
}
}
pub fn main() {
printf("%d\n", MathUtils.square(5));
}
کپسولهسازی
- فیلدها و متدها بهطور پیشفرض خصوصی هستند؛
pubآنها را عمومی میکند - متدها را نمیتوان به struct خارج از ماژول تعریفکننده آن اضافه کرد — هیچ monkey-patching یا رفتار پراکندهای وجود ندارد
- بدون وراثت — ترکیب به جای سلسلهمراتب
قوانین سیستم نوع
کوروش بهطور ایستا تایپشده (statically typed) با قوانین سختگیرانه طراحی شده برای پیشبینیپذیری است.
استنتاج نوع
استنتاج نوع در بافتهای کاملاً اعلام شده کار میکند — هر متغیر باید در نقطه اعلان دارای یک نوع شناخته شده باشد، چه از طریق مقداردهنده یا یک حاشیهنویسی صریح:
const object: Box<int> = Box.new(10); // OK: explicit type annotation
const value = Box.new(10); // OK: type inferred from initializer
// const object; // ERROR: type cannot be inferred later
نامهای مستعار نوع
کلیدواژه type نامهای مستعار ایجاد میکند — کامپایلر آنها را بهطور شفاف گسترش میدهد:
type rune = uint32;
type Handler = fn(int, int) void;
مقداردهی صفر
هر متغیری که بدون مقدار اولیه صریح اعلام شود بهطور خودکار با صفر مقداردهی میشود:
var x: int; // initialized to 0
var p: void*; // initialized to null
var arr: int[10]; // all elements set to 0
مدل عملکرد قابل پیشبینی
کوروش بر روی LLVM ساخته شده و بهصورت ahead-of-time کامپایل میشود. مدل عملکرد ساده است:
- بدون تخصیص پنهان: کوروش هرگز از طرف شما حافظه تخصیص نمیدهد. هر تخصیص صریح است (
malloc، تخصیصدهنده سفارشی، تخصیص stack). - بدون کپی پنهان: معناشناسی عبور ساختارها صریح است (کپی بهوسیله مقدار، بهوسیله اشارهگر). کامپایلر بهینهسازی خواهد کرد اما مدل پیشفرض قابل مشاهده است.
- جنریکهای با هزینه صفر: مونومورفایزیشن کدی معادل تخصصیسازیهای دست نویس تولید میکند.
- استراتژی توزیع صریح: توزیع ایستا برای جنریکهای محدود شده تضمین میشود؛ توزیع پویا نیاز به کلیدواژه
dynamicدارد.
آنچه مینویسید بهطور نزدیک منعکسکننده آن چیزی است که اجرا میشود. هیچ انتزاع زمان اجرای درجشده توسط کامپایلر، شمارش مرجع، یا چرخههای جمعآوری زباله برای محاسبه وجود ندارد.
سیستم ماژول
ماژولهای کوروش فایلهایی هستند که با مسیر حل میشوند و دید صریح دارند:
// auth_utils.cyrus
pub fn login(user: char*) { /* visible outside */ }
fn internal_helper() { /* private to this file */ }
// main.cyrus
import auth_utils{login};
pub fn main() {
login("Cyrus"); // imported by name
}
هیچ پیشپردازندهای، هیچ گنجاندن متنی، هیچ include guard وجود ندارد — فقط importهای معنایی با مرزهای وابستگی واضح.
آنچه کوروش از انجام آن خودداری میکند
این استثناها عمدی هستند، نه محدودیت:
| شامل نمیشود | دلیل |
|---|---|
| بدون جمعآوری زباله | GC زمانهای توقف غیرقابل پیشبینی اضافه میکند و تخصیص را از توسعهدهنده پنهان میکند |
| بدون بررسیکننده وام (borrow checker) | بررسی وام قوانین پیچیدهای را تحمیل میکند که آنچه را توسعهدهنده میتواند بیان کند محدود میکند |
| بدون مدل همروندی | در دست طراحی — در حال ارزیابی گزینهها؛ کتابخانهها و primitivesهای سیستمعامل نیازهای فعلی را برآورده میکنند |
| بدون سیستم کامل شیگرایی | سلسلهمراتب وراثت جریان داده را مبهم میکند؛ ترکیب صریح است |
| بدون拡انش متد در خارج از ماژولها | همه رفتارهای متصل به یک نوع در یک ماژول قرار دارند — بدون پراکندگی |
| بدون ترکیب پارادایم | کوروش در هسته خود رویهای (procedural) است؛ انتزاعات تابعی و عبارات متراکم اجتناب میشوند |
جایگاه کوروش
هر زبانی بدهبینیهایی دارد. کوروش یک فلسفه جایگزین است: کنترل صریح، حداقل انتزاع، حداکثر اختیار برای توسعهدهنده.
- اگر C شما را ناامید میکند زیرا ویژگیهای زبانی کافی برای برنامهنویسی مدرن ارائه نمیدهد و فاقد سیستم ماژول مناسب است، کوروش همان سطح کنترل را با ارگونومی بهتر به شما میدهد.
- اگر بررسیکننده وام راست مشکلاتی را حل میکند که شما ندارید و بار حاشیهنویسی با مورد استفاده شما سازگار نیست، کوروش کنترل را بدون سیستم طول عمر به شما میدهد.
- اگر comptime و متاپروگرمینگ زیگ برای پروژه شما پیچیدگی غیرضروری است، کوروش مدل اجرا را ساده و قابل پیشبینی نگه میدارد.
- اگر زمان اجرا و GC گو برای پروژه سیستمی شما نامناسب است و ساختارهای داده غنیتر (enum، جنریک، واسط) با کنترل دستی حافظه میخواهید، کوروش آن ابزارها را در اختیار شما قرار میدهد.
کوروش سعی در جایگزینی هیچیک از این زبانها ندارد. این یک نقطه متفاوت در همان طیف است.
کوروش برای چه کسانی است
- برنامهنویسان سیستمی آشنا با C که ارگونومی بهتری بدون از دست دادن کنترل میخواهند
- توسعهدهندگانی که زیرساخت سطح پایین میسازند (runtimeها، هستهها، سیستمهای نهفته، شبکه)
- تیمهایی که قابلیت نگهداری بلندمدت و کد صریح را بر ویژگیهای راحتطلبانه اولویت میدهند
- هر کسی که ترجیح میدهد مستقیماً درباره مدل ماشین استدلال کند تا از طریق یک لایه ایمنی
کوروش برای چه کسانی نیست
- مبتدیان مطلق بدون پیشینه برنامهنویسی سیستمی
- توسعهدهندگانی که انتظار مدیریت خودکار حافظه دارند
- کسانی که به دنبال تضمینهای ایمنی زمان کامپایل قوی بدون درک ماشین زیرین هستند
- کاربردهایی که اثبات ایمنی حافظه بهصورت ایستا یک نیاز سخت است
محدودیت طراحی: ایدههای اثباتشده
کوروش به دنبال تازگی به خاطر تازگی نیست. هر ویژگی جایگاه خود را با حل یک مشکل واقعی در برنامهنویسی سیستمی به دست میآورد — اثبات شده توسط دههها استفاده در زبانهای خانواده C، بهبود یافته به شکلی سازگار و صریح.
هدف اختراع نیست. هدف گزینش است: بهترین ایدهها را انتخاب کنید، آنها را بهطور منسجم یکپارچه کنید، و پیچیدگی ناشی از تکامل موردی را حذف کنید.
ویژگیهایی که هنوز در حال بررسی هستند
کوروش در حال توسعه فعال است. ویژگیهای زیر در حال تحقیق و طراحی هستند و زمانی که arrive شوند بر اساس همان اصول شکل خواهند گرفت:
- مدل همروندی — در حال ارزیابی گزینهها از جمله فیبرها، کوروتینها و I/O ناهمگام؛ هنوز تصمیمی گرفته نشده است
- سیستم ماکرو/متاپروگرمینگ — بررسی رویکردهای تولید کد زمان کامپایل
- مدیریت بسته — بررسی مکانیزمهای حل وابستگی و توزیع
سخن پایانی
کوروش سعی نمیکند امنترین زبان، انتزاعیترین یا نوآورانهترین زبان باشد.
آن تلاش میکند این گونه باشد:
- صریح — تا بتوانید بدون خواندن ذهن کامپایلر ببینید کد چه میکند.
- قابل کنترل — تا بتوانید ماشین را به آنچه نیاز دارید وادار کنید، بدون جنگیدن با زبان.
- قابل نگهداری — تا کد امروز سالهای بعد همچنان قابل فهم باشد.
زبانی که در آن توسعهدهنده مسئول باقی میماند — بدون تظاهر به اینکه ماشین وجود ندارد.
کوروش: زیرساخت سیستمی، کنترل صریح.

