بازنویسی ۳۰۰ هزار خط از Rust به Zig: گزارش یک سال و نیم
خلاصهٔ کاملتر
تیم سازندهٔ کامپایلر زبان Roc یک سال و نیم صرف بازنویسی ۳۰۰ هزار خط کد Rust به Zig کرد و تازگی به نقطهٔ عطف برابری قابلیت با کامپایلر اصلی رسید. نویسنده میگه چون پروژهٔ Bun اخیراً گزارش تجربهش رو از بازنویسی در جهت مخالف (از Zig به Rust) منتشر کرده، وقت خوبی برای مرور این مسیره. او شفافسازی میکنه که این دو بازنویسی قابل مقایسه نیستن: مال Bun یک پورت مستقیم بود، در حالی که تیم Roc اصلاً بهخاطر حجم تغییراتی که میخواستن بدن تصمیم به بازنویسی گرفت.
از قابلیتهای تازهای که این بازنویسی باز کرد، دوتاش برجستهست. کامپایلر جدید حین توسعه بهصورت خودکار بارگذاری داغ کد انجام میده: میشه یک وبسرور رو اجرا کرد، کدش رو حین اجرا عوض کرد و درخواست بعدی با کد جدید سرو میشه — چیزی که برای زبانهای مفسری استاندارده ولی برای زبانهای کامپایلشدهٔ پرسرعت نه. دومی کراسکامپایل با خروجی بایتبهبایت یکسانه، مستقل از اینکه روی مک بیلد بگیری یا جای دیگه.
نمونهٔ فنی جالبش تطبیق الگو با درونیابی رشتهست: مسیریابی HTTP با الگوهایی مثل "/users/${id}/${page}" نوشته میشه، ولی برخلاف قالبهای رشتهای که در زمان اجرا پارس میشن، این یک قابلیت زبانیه که در زمان کامپایل type-safe هست و کل قطعه صفر تخصیص حافظهٔ هیپ داره.
دلیل انتخاب Zig چهار محور داشت: زمان بیلد که با cargo نقطهدرد بزرگی شده بود؛ کنترل حافظه، چون اکوسیستم Rust معمولاً یک allocator سراسری فرض میکنه در حالی که کل اکوسیستم Zig روی allocatorهای دانهریز و چیدمان struct-of-arrays بنا شده؛ ارتباط اکوسیستم، چون برای نیازهای خاص اونها کد بیشتری در Zig موجود بود؛ و کمک به کد ناایمن، چون کد ناایمن برای یک کامپایلر نادر نیست — اونها حدود ۱۲۰۰ مورد unsafe در ۳۰۰ هزار خط Rust داشتن.
جالبترین بخش مقاله آمار واقعی باگهاست. طبق دستهبندی ایشوهای Roc، نسخهٔ Rust ۲۱ باگ خرابی حافظه از مجموع ۲۵۹۶ باگ داشته و نسخهٔ Zig ده مورد از مجموع ۴۳۱. ولی نکتهٔ کلیدی اینه که هیچکدوم از اون ۲۱ مورد در خود منطق کامپایلر نبوده: کامپایلر دستور ماشین تولید میکنه و وقتی خروجی اشتباه باشه، خرابی حافظه در برنامهٔ کامپایلشده رخ میده.
از ده مورد سمت Zig هم هشتتاش miscompilation بوده و فقط دو مورد واقعاً در خود کامپایلر: هر دو use-after-free در گزارش خطا، با علامت یکسانِ نمایشنشدن نام فایل. نویسنده اذعان میکنه borrow checker زبان Rust هر دو رو میگرفت.
جمعبندیش از این جدول صادقانهست: بعد از ۱۸ ماه توسعه و صدها گزارش باگ، انتخاب هر کدوم از گزینهها — Zig با ReleaseFast، Zig با ReleaseSafe، یا Rust — تفاوت محسوسی در عمل برای این پروژه نمیساخت. اون تأکید میکنه نیاز هر پروژه فرق داره: Bun که با مقادیر زبالهٔ جاوااسکریپت و حافظهٔ دستی سروکار داره دقیقاً همین دسته باگها براش دردسر بزرگی بوده، ولی کامپایلر Roc اون چالش رو نداره.
در بخش زمان بیلد، عددها چشمگیرن. روی نسخهٔ Rust، بیلد افزایشی در طول همین ۱۸ ماه از ۱۰ ثانیه به ۳.۴ ثانیه بهبود پیدا کرده بود که نویسنده صریحاً بابتش از مشارکتکنندههای Rust تشکر میکنه. ولی zig build --watch -fincremental تغییر در حدود ۴۵۰ هزار خط کد Zig رو در حدود ۳۵ میلیثانیه بازسازی میکنه — یعنی یکصدم، و اون هم روی کدبیسی که ۵۰٪ خط بیشتری داره. تنها مشکل اینه که نسخهٔ پایدار فعلی Zig باگی داره که این حالت رو روی کدبیس اونها میشکنه و فعلاً منتظر انتشار پایدار بعدی موندن.
نکات کلیدی:
- بازنویسی ۴۸۷ روز طول کشید و به برابری قابلیت رسید؛ نسخهٔ ۰.۱.۰ امسال هدفگذاری شده
- از ده باگ خرابی حافظه در نسخهٔ Zig، فقط دوتا در خود کامپایلر بود
- هر دو مورد use-after-free در گزارش خطا بودن که borrow checker میگرفتشون
- بیلد افزایشی Zig حدود ۳۵ میلیثانیه در برابر ۳.۴ ثانیهٔ Rust
- کامپایلر جدید بارگذاری داغ کد و کراسکامپایل با خروجی بازتولیدپذیر داره
- نتیجهگیری نویسنده: برای این پروژه انتخاب زبان تفاوت ایمنی محسوسی نساخت




