زبان برنامهنویسی Goose؛ سیستمزبانِ امن بدون heap و GC
نویسنده: آرش لطیفی
زبان Goose چطور بدون heap allocator و garbage collector و حتی بدون lifetime annotation حافظه امن میکند و چرا در بنچمارکهای خودش از C++ و Rust سریعتر است
TL;DR — زبان جدید Goose حافظه را با «دیتااستک» بهجای heap مدیریت میکند: نه GC دارد، نه allocator، نه حتی lifetime annotation؛ در بنچمارکهای خودش از C++ ایدیوماتیک ۳.۳ برابر و از safe Rust هم سریعتر است، ولی مدل حافظهاش محدودیتهای جدی دارد که باید بشناسی.
از کجا آمد؟
۲۴ اکتبر ۲۰۲۶، معرفی زبان Goose در Hacker News حدود ۴۴۴ امتیاز گرفت — بعد از ماجرای Brotli و CVE-2026-111497، بحث «memory safety بدون GC» دوباره داغ شده و Goose دقیقا وسط همین بحث پریده. سازندهاش (Sander van Rossen، معروف به aardappel سازنده TreeSheets) زبان را کامل مستند کرده: tutorial، reference، specification، ۳۱ نمونه برنامه و بنچمارک با اعدادِ باخت هم که داخلش باشد.
اسمش عمدا کمدی است، ولی ادعاها جالبن. بیایم ببینیم واقعا چه خبر است.
مدل حافظه: استک، نه heap
ایده مرکزی Goose یک جمله است: هر مقدار داینامیک، inline روی یک دیتااستک زندگی میکند و با خارج شدن از scope آزاد میشود. نقطه.
در یک برنامه Goose فقط سه چیز حافظه دارد: call stack سیستم، دیتای static، و N تا دیتااستک که کامپایلر تعدادشان را خودش درمیآورد. هر استک یک رزرو بزرگ address-space با bump pointer است — هیچ چیز دیگری وجود ندارد. در هر لحظه حداکثر یک مقدار resizable روی هر استک زنده است که همیشه روی تُپ است، پس:
- رشد حافظه فقط جلو بردن یک pointer است؛ نه reallocation، نه کپی، نه چک capacity
- آزاد کردن ساختاری با یک میلیون المان = یک store
- رفرنسها هیچوقت جابهجا نمیشوند، پس بعد از رشد آرایه هم معتبر میمانند
این نکته آخر در C++ یا Rust ممکن نیست: Vec<T> موقع رشد ممکن است reallocate کند و رفرنسها را خراب کند؛ برای همین safe Rust در بنچمارکهای متناظر از index (u32) استفاده میکند. در Goose رفرنس به المان اول بعد از یک میلیون push هنوز valid است و میشود مستقیم رویش نوشت.
| ویژگی | C++ | Rust (safe) | Goose | |---|---|---|---| | مدیریت حافظه | دستی + RAII | allocator + Drop | bump stack، free با scope | | امنیت رفرنس | UB ممکن | borrow checker + lifetime | استنتاج static root، بدون annotation | | رفرنس بعد از رشد آرایه | dangling | معمولا index | معتبر میماند | | GC / refcount | ندارد | ندارد | ندارد |
امنیت بدون حتی یک annotation
جذابترین بخش برای برنامهنویسهای Rust: Goose هیچ lifetime syntax ندارد. نه 'a، نه قواعد aliasing و exclusivity، نه unsafe. کامپایلر برای هر رفرنس یک static root استنتاج میکند — یعنی متغیری که عمر هدف رفرنس را محدود میکند — و اگر رفرنس بتواند از مالکاش بیشتر زنده بماند یا از نوع ناسازگاری به هدف دسترسی پیدا کند، reject میشود.
چرا سریع است؟
اعداد خود پروژه (که لینک نتایجش را منتشر کرده و «losses included» میگوید): در ۱۶ بنچمارک،
- ۳.۳x سریعتر از C++ ایدیوماتیک با ۱.۹x حافظه کمتر
- ۱.۱۶x سریعتر از C++ دستبهینه با ۱.۳x حافظه کمتر
- ۱.۱۲x سریعتر از بهترین safe Rust با ۱.۲x حافظه کمتر
سرعت از جاهایی میآید که در زبانهای heap-based هزینه ثابتاند: چیدمان دیتای inline (رکورد ۱۶۰ بایتی با یک allocation در C++، در Goose ۲۹ بایت و صفر allocation)، enumهای variable-mode که هر value فقط اندازه variant خودش را میگیرد (نه بزرگترین variant؛ در بنچمارک متناظر ۴x حافظه کمتر و ۲x سریعتر از Rust enum)، و «لینک»های نسبتی ۱ تا ۴ بایتی بهجای pointer.
چیزهایی که تازهاند
چند قابلیت Goose خارج از بحث حافظه هم جالب است:
- دیتای آماده ذخیرهسازی: ساختارهای ساختهشده از لینکهای نسبتی position-independent اند؛ یعنی ساختار دادهات خودش فرمت فایل است. ذخیره = یک write، لود = یک read بهعلاوه verification که بایتهای hostile را reject میکند.
- خطا بدون unwinder:
return err from loadاز هر تعداد فریم بالا میرود، بهصورت statically checked، بدونResultو بدون?در هر خط. - ترد بدون shared memory: هر worker بهصورت یک برنامه جدا کامپایل میشود با حافظه خودش؛ دادهها بهصورت flat از طریق queueهای تایپشده با
memcpyرد و بدل میشوند. data race، lock و atomic در زبان وجود ندارد. - C in, C out: خروجی کامپایل یک فایل C است، پس هرجا C کامپایلر باشد اجرا میشود و فراخوانی C با
extern fnمستقیم است. بکاند TinyCC هم همراهش است که بدون build step درهمان پروسه کامپایل و اجرا میکند. - Generics بدون هزینه: پارامتر تایپنشده generic است و function valueها موجودیت compile-timeاند؛
xs.filter() { it > 0 }به همان لوپی که به نظر میرسد کامپایل میشود و نتیجه را مستقیم در مقصد میسازد.
هزینههای واقعی — این بخش را نخوانی، دام میفتی
هر مدل حافظهای محدودیت خودش را دارد و Goose مستندسازی صادقانهای از اینها دارد:
- یک مقدار resizable زنده در هر استک: اگر دو ساختار بزرگ را همزمان باید رشد بدهی، مدل بهت نمیخورد یا باید کپی کنی.
- data-oriented یا هیچ: الگوهای heavy pointer-based (مثل گرافهای پر از رفرنس دوطرفه) با ساختار استکِ خطی راحت درنمیآیند؛ باید به LIFO فکر کنی.
- اکوسیستم صفر: زبان تازه متولد شده؛ standard library و پکیجها در حد نوزادی است و در تولید استفاده ازش ریسک واقعی دارد.
- بنچمارکهای خودش: این اعداد روی ۱۶ بنچمارکِ انتخابی خود پروژه است. پروژه خودش اعداد باخت را هم منتشر کرده و این اتفاقا نشانه خوبی است، ولی «روی ورکلود من سریعتر است» را باید خودت با benchmark اندازه بگیری.
برای چه کسی معنا دارد؟
واقعبینانه: Goose فعلا برای پروژه تولیدی شما گزینه نیست — برای یادگیری، بازی با مدلهای حافظهای و شاید ابزارهای کوچک پرفورمنس-سنسیتِو جذاب است. ارزش اصلیاش شاید این باشد که ثابت میکند رابطه «امنیت حافظه ← GC یا borrow checker سنگین» اجباری نیست؛ راه سومی وجود دارد: مدل حافظه را طوری طراحی کن که کل مسئله حذف شود. برای کسی که تازه ماجرای Brotli و باگهای حافظه Node را پچ کرده، این زاویه دید کمپانی ندارد.
- [ ] Model حافظه Goose را با خواندن specification بفهم، نه با رویا
- [ ] Tutorial و یک نمونه از ۳۱ برنامه آماده را اجرا کن (TinyCC بدون build step اجرا میکند)
- [ ] یک ابزار کوچک CPU-bound خودت را در Goose بازنویسی کن و با نسخه فعلیت benchmark کن
- [ ] جدول losses در بنچمارکها را ببین تا بدانی کجا Goose میبازد
- [ ] در تولید استفاده نکن — حداقل فعلا
FAQ
Q: Goose واقعا از Rust سریعتر است؟
در بنچمارکهای خودِ پروژه (۱۶ عدد، با انتشار اعداد باخت) بله — حدود ۱.۱۲x سریعتر از بهترین safe Rust با حافظه کمتر. این با اکوسیستم بلوغ Rust و کیفیت در ورکلودهای واقعی یکی نیست؛ عددهای پروژه را ملاک نهایی قرار نده.
Q: اگر heap و GC ندارد، پس حافظه داینامیک چطور مدیریت میشود؟
با دیتااستک: هر مقدار داینامیک inline روی یک استک bump-allocated زندگی میکند و وقتی scopeاش تمام شود، رشد pointer برمیگردد و فضا آزاد میشود. کامپایلر تعداد و اندازه استکها را statically تعیین میکند.
Q: برای کارهای روزمره وب میشود استفاده کرد؟
نه. خروجی Goose یک فایل C است و رابط مستقیم با C دارد؛ برای ابزار سیستمی، embedded و کارهای CPU-heavy طراحی شده. اکوسیستم، کتابخانه وب و پختگی لازم برای توسعه وب را ندارد.