توابع

    توابع در کوروش با استفاده از کلیدواژه fn، به دنبال آن نام تابع، لیست پارامترها و نوع بازگشتی تعریف می‌شوند. توابع به طور پیش‌فرض خصوصی هستند و می‌توانند با مشخص‌کننده‌های دسترسی برای کنترل دید، پیوند (linkage) و رفتار بهینه‌سازی حاشیه‌نویسی شوند.

    تعریف تابع پایه

    یک تابع ساده که دو عدد صحیح را جمع می‌کند:

    fn sum(x: int, y: int) int {
        return x + y;
    }
    
    • پارامترها به صورت صریح تایپ شده‌اند.
    • نوع بازگشتی بعد از لیست پارامترها قرار می‌گیرد.
    • return برای توابع غیر void الزامی است.

    بازگشت (Recursion)

    توابع می‌توانند به صورت بازگشتی خود را فراخوانی کنند.

    fn fibonacci(n: int) int {
        if (n == 0) {
            return 0;
        } else if (n == 1) {
            return 1;
        } else {
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }
    
    pub fn main() {
        fibonacci(10); // evaluates to 55
    }
    

    بازگشت از معنای معمول فراخوانی و رفتار پشته پیروی می‌کند.

    اصلاح‌کننده‌های تابع

    پس از تعریف دید و پیوند یک تابع (مانند extern)، کوروش اجازه می‌دهد توابع با استفاده از اصلاح‌کننده‌های تابع بیشتر اصلاح شوند.

    اصلاح‌کننده‌های تابع کنترل می‌کنند که یک تابع چگونه:

    • پیوند (link) می‌یابد
    • درون‌خطی (inline) می‌شود
    • در سطح ABI فراخوانی می‌شود
    • در باینری قرار می‌گیرد
    • توسط کامپایلر بهینه‌سازی یا محدود می‌شود

    کوروش اصلاح‌کننده‌های تابع را به گروه‌های معنایی متمایز طبقه‌بندی می‌کند. این تفکیک عمدی است: زبان را متعامد نگه می‌دارد و از ترکیب‌های مبهم جلوگیری می‌کند.

    دید (Visibility)

    کنترل می‌کند که تابع از کجا قابل ارجاع است.

    pub fn compute() int { ... }
    

    اگر هیچ اصلاح‌کننده دیدی مشخص نشود، تابع نسبت به ماژول خود خصوصی است.

    پیوند (Linkage)

    کنترل می‌کند که نماد تابع چگونه توسط پیونددهنده (linker) حل شود.

    extern fn printf(fmt: const char*, ...) int32;
    

    فرم‌های پیوند اضافی برای موارد استفاده پیشرفته وجود دارد:

    • extern: نماد خارج از ماژول تعریف شده است
    • weak: پیوند ضعیف
    • linkonce:语义 پیوند یک‌بار (معمولاً برای کدهای حذف تکراری)

    فقط یک اصلاح‌کننده پیوند ممکن است در هر تابع مشخص شود.

    درون‌خطی‌سازی (Inlining)

    راهنمایی می‌کند که کامپایلر چگونه با فراخوانی‌های تابع رفتار کند.

    inline fn add(a: int, b: int) int { ... }
    

    اصلاح‌کننده‌های درون‌خطی‌سازی پشتیبانی‌شده:

    • inline: ترجیح درون‌خطی‌سازی
    • noinline: منع درون‌خطی‌سازی
    • alwaysinline: اجبار درون‌خطی‌سازی در صورت امکان

    اصلاح‌کننده‌های درون‌خطی‌سازی بر بهینه‌سازی تأثیر می‌گذارند اما معنای کد را تغییر نمی‌دهند.

    کنترل پرولوگ (Prologue Control)

    کنترل می‌کند که آیا کامپایلر یک پرولوگ و اپیلوگ برای تابع تولید می‌کند.

    naked fn interrupt_handler() void { ... }
    

    Naked هیچ راه‌اندازی یا پاک‌سازی پشته تولید نمی‌کند. این برای کدهای سطح پایین مانند هندلرهای وقفه یا پل‌های اسمبلی دست‌نویس در نظر گرفته شده است.

    اصلاح‌کننده‌های خروجی (Export Modifiers)

    نحوه خروجی تابع را در سراسر مرزهای باینری کنترل می‌کنند.

    pub dllexport fn api_entry() void { ... }
    

    انواع خروجی پشتیبانی‌شده:

    • dllimport
    • dllexport

    این اصلاح‌کننده‌ها مختص سکو (platform) هستند و عمدتاً هنگام تولید کتابخانه‌های مشترک استفاده می‌شوند.

    قرارداد فراخوانی (Calling Convention)

    قرارداد فراخوانی ABI مورد استفاده هنگام فراخوانی تابع را مشخص می‌کند.

    callconv(fastcall) fn memcpy(dst: void*, src: void*, n: uint) void;
    

    قراردادهای فراخوانی پشتیبانی‌شده عبارتند از:

    • c
    • system
    • sysv64
    • win64
    • stdcall
    • fastcall
    • thiscall
    • vectorcall
    • interrupt
    • naked
    • aapcs
    • cold
    • fast

    فقط یک قرارداد فراخوانی ممکن است در هر تابع مشخص شود.

    پرچم‌های اختیاری تابع

    پرچم‌های اختیاری راهنمایی‌های معنایی یا بهینه‌سازی اضافی ارائه می‌دهند.
    چندین پرچم اختیاری ممکن است روی یک تابع اعمال شود.

    noreturn cold fn fatal_error(msg: char*) void { ... }
    

    پرچم‌های اختیاری پشتیبانی‌شده عبارتند از:

    • noreturn: تابع هرگز بازنمی‌گردد
    • nounwind: تابع پشته را unwind نمی‌کند
    • cold: احتمال اجرا کم است
    • hot: مسیر بحرانی از نظر عملکرد
    • optsize: بهینه‌سازی برای اندازه
    • optnone: غیرفعال کردن بهینه‌سازی‌ها
    • nosanitize(name): غیرفعال کردن یک sanitizer خاص

    کامپایلر ترکیب‌ها را برای جلوگیری از پرچم‌های متضاد اعتبارسنجی می‌کند.

    قراردهی در بخش (Section Placement)

    توابع ممکن است در بخش‌های خاص باینری قرار داده شوند.

    section(".init") fn early_init() void { ... }
    

    هنگامی که توسط هدف پشتیبانی می‌شود، ممکن است چندین ویژگی قراردهی مشخص شود.
    این معمولاً برای کد هسته، سیستم‌های نهفته یا کنترل دقیق پیونددهنده استفاده می‌شود.

    آرگومان‌های متغیر (Variadic Arguments)

    کوروش از دو نوع آرگومان متغیر پشتیبانی می‌کند که به توابع امکان می‌دهد تعداد متغیری از پارامترها را بپذیرند.

    آرگومان‌های متغیر به سبک C

    توابع متغیر به سبک C از نشانه‌گذاری ... درست مانند C استفاده می‌کنند. اینها معمولاً برای ارتباط با کتابخانه‌های C (مانند printf) یا زمانی که ایمنی نوع مطرح نیست استفاده می‌شوند.

    extern fn printf(format: const char*, ...) int;
    
    fn main() {
        printf("Hello %s, number = %d\n", "Cyrus", 42);
    }
    
    • از این سبک عمدتاً برای سازگاری با C و APIهای سطح پایین استفاده کنید.

    برای دسترسی و پیمایش روی آرگومان‌های متغیر از درون تابع خود، بخش Builtins در مستندات را برای جزئیات در مورد @va_list، @va_start، @va_arg و @va_end ببینید.

    نام تابع

    کوروش به شما امکان می‌دهد یک نام متفاوت در سطح ABI (لینکر) برای یک تابع نسبت به نام کوروشی آن مشخص کنید. این کار با استفاده از کلیدواژه as بعد از اعلان انجام می‌شود.

    extern fn printf(fmt: string, ...) int as my_printf;
    
    fn main() {
        my_printf("Hello, %s!\n", "Cyrus");
    }
    
    • تابع در سطح ABI به عنوان printf اعلان می‌شود، بنابراین با نماد واقعی printf در کتابخانه استاندارد C پیوند می‌خورد.
    • در داخل کوروش، با این حال، تابع به عنوان my_printf ارجاع داده می‌شود.
    • این به شما امکان می‌دهد از تداخل نام‌ها جلوگیری کنید یا توابع کتابخانه موجود را با نام‌های توصیفی‌تر بپیچید.

    لامبداها

    لامبداها در کوروش توابع بی‌نام هستند. آنها شبیه توابع معمولی به نظر می‌رسند و رفتار می‌کنند، اما نیازی به نام ندارند. شما آنها را به صورت درون‌خطی تعریف می‌کنید، به متغیرها تخصیص می‌دهید، جابه‌جا می‌کنید یا درون structها جاسازی می‌کنید.

    برخلاف closures در برخی زبان‌های دیگر، لامبداهای کوروش محیط اطراف را capture نمی‌کنند. آنها اشیاء تابع خالص هستند که با یک امضای ثابت و مشخص تعریف می‌شوند.

    انواع تابع

    هر لامبدا یک نوع تابع دارد که پارامترها و نوع بازگشتی آن را توصیف می‌کند.

    مثال:

    type HandlerFn = fn(int, int) void;
    

    تعریف یک لامبدا

    نحو ساده است:

    pub fn main() {
        const handler = fn(x: int, y: int) int {
            return x + y;
        };
    
        printf("%d\n", handler(5, 7));
    }
    

    لامبداها می‌توانند از توابع بازگردانده شوند یا مانند هر نوع دیگری به متغیرها تخصیص یابند:

    fn make_double_handler() fn(float64) float64 {
        return fn(x: float64) float64 {
            return x * 2.0;
        };
    }
    
    pub fn main() {
        const handler = make_double_handler();
        const result = handler(15.3);
    }
    

    متغیرهای سراسری یا محلی نیز می‌توانند تعاریف لامبدا را نگه دارند:

    var handler = fn(x: float64, y: float64) float64 {
        return 1.5;
    };
    
    pub fn main() {
        const compute_handler = fn(cond: bool) fn(float64, float64) float64 {
            if (cond) {
                return fn(x: float64, y: float64) float64 {
                    return y + x;
                };
            } else {
                return fn(x: float64, y: float64) float64 {
                    return -y * 2.0;
                };
            }
        };
    
        printf("%f ", (compute_handler(true))(2.0, 3.5)); // result: 5.500000
        printf("%f ", (compute_handler(false))(2.0, 3.5)); // result: -7.000000
    }
    

    تخصیص به متغیرها

    می‌توانید لامبداها را در متغیرها ذخیره کنید:

    pub fn main() {
        var add = fn(x: int, y: int) int {
            return x + y;
        };
    
        printf("%d\n", add(2, 3)); // prints 5
    }
    

    عدم Capture محیط

    در کوروش، لامبداها closures نیستند. این بدان معناست که آنها متغیرهای scope اطراف خود را capture نمی‌کنند. برای مثال:

    fn main() {
        var base = 10;
    
        var adder = fn(x: int) int {
            return x + base; // base is not accessible here
        };
    }
    

    نقطه ورودی main

    هر برنامه کوروش اجرای خود را از یک تابع نقطه ورودی ویژه به نام main آغاز می‌کند. این تابع توسط پیونددهنده در سطح ABI کشف شده و به عنوان نماد شروع فرآیند استفاده می‌شود.

    برای اطمینان از اینکه main به عنوان یک نماد خارج از ماژول تعریف‌کننده خود قابل مشاهده است (تا پیونددهنده بتواند آن را به عنوان ورودی برنامه bind کند)، باید به عنوان یک تابع عمومی اعلان شود:

    pub fn main() {
        // program entry point
    }
    

    ثابت‌بودن پارامترها

    به طور پیش‌فرض، پارامترهای توابع در کوروش تغییرناپذیر (immutable) هستند. با این حال، می‌توانید به طور صریح از کلیدواژه const در لیست پارامترها استفاده کنید تا اعمال و مستند کنید که مقدار آرگومان نمی‌تواند درون بدنه تابع تغییر کند.

    fn sum(const x: int, const y: int) int {
        x = 10; // ERROR!
    
        return x + y;
    }
    
    • تغییرناپذیری: وقتی یک پارامتر const علامت‌گذاری می‌شود، هر تلاش برای تخصیص مجدد مقدار آن درون تابع منجر به خطای زمان کامپایل می‌شود.
    • قصد: این به طور صریح به کامپایلر و سایر توسعه‌دهندگان signal می‌دهد که تابع این ورودی‌ها را فقط خواندنی در نظر می‌گیرد.
    • بهینه‌سازی: const بودن صریح به کامپایلر اجازه می‌دهد فرضیات قوی‌تری درباره جریان داده داشته باشد که به طور بالقوه منجر به تخصیص بهتر ثبات و بهینه‌سازی می‌شود.