جنریک‌ها

    جنریک‌ها (Generics) به شما امکان می‌دهند کدی بنویسید که مستقل از انواع خاص باشد. در کوروش، فقط انواع نام‌دار و توابع می‌توانند جنریک باشند. انواع بدون نام (مانند تاپل‌های خام) و توابع ناشناس (lambdas) از پارامترهای جنریک پشتیبانی نمی‌کنند.

    توابع جنریک

    یک تابع با افزودن پارامترهای نوع درون براکت‌های زاویه‌ای <T> جنریک می‌شود.

    // یک تابع همانی ساده برای آرایه‌ای با اندازه ثابت
    fn identity_pair<T>(values: T[2]) T[2] {
        return values;
    }
    
    pub fn main() {
        // استنتاج خودکار
        const p1 = identity_pair({1, 2});
    
        // آرگومان‌های نوع صریح
        const p2 = identity_pair<int>({3, 4});
    
        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ها و Enumهای جنریک

    Structها، Unionها و Enumها همگی می‌توانند پارامترهای نوع دریافت کنند.

    Structها و استنتاج نوع (_)

    می‌توانید از زیرخط _ استفاده کنید تا به کامپایلر بگویید یک آرگومان نوع خاص را استنتاج کند در حالی که شما سایر آرگومان‌ها را به‌طور دستی ارائه می‌دهید.

    struct Pair<K, V> {
        pub key: K;
        pub value: V;
    }
    
    pub fn main() {
        // K به صورت صریح (uint)، اما اجازه دهید کامپایلر V را استنتاج کند
        const entry = Pair<uint, _> {
            key: 1,
            value: "Cyrus"
        };
    }
    

    Enumهای جنریک

    جنریک‌ها هنگامی که با enumها برای نمایش مقادیر اختیاری یا نتایج ترکیب می‌شوند، قدرتمند هستند.

    enum Option<T> {
        Some(T),
        None
    }
    
    fn print_opt(opt: Option<int>) {
        switch (opt) {
            case .Some(val) => {
                printf("Value: %d\n", val);
            }
            case .None => {
                printf("Nothing\n");
            }
        }
    }
    

    متدهای جنریک

    حتی اگر یک struct جنریک نباشد، متدهای آن می‌توانند جنریک باشند. اگر یک struct جنریک باشد، متدهای آن می‌توانند پارامترهای نوع اضافی معرفی کنند.

    struct Math {
        // یک متد جنریک در یک struct غیر جنریک
        pub fn add<T>(x: T, y: T) T {
            return x + y;
        }
    }
    
    struct Box<T> {
        value: T;
    
        // 'Self' به Box<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() {
        // از uint64 پیش‌فرض برای E استفاده می‌کند
        var res = Result<char*, _> { value: "Success", error_code: 0 };
    }
    

    نام‌های مستعار نوع جنریک

    از نام‌های مستعار نوع (type aliases) می‌توان برای ایجاد «اختصار» برای انواع جنریک پیچیده استفاده کرد، یا خودشان می‌توانند جنریک باشند.

    struct Pair<K, V> {
        key: K;
        value: V;
    }
    
    // نام مستعار غیر جنریک برای یک نمونه جنریک خاص
    type IntPair = Pair<int, int>;
    
    // یک نام مستعار جنریک
    type Handler<T> = fn(T) void;
    
    pub fn main() {
        const log: Handler<int> = fn(x: int) void {
            printf("%d", x);
        };
    }
    

    رابط‌های جنریک

    رابط‌ها (Interfaces) می‌توانند قراردادهای جنریک تعریف کنند. یک struct می‌تواند یک نمونه‌سازی خاص از یک رابط جنریک را پیاده‌سازی کند یا خودش جنریک باشد تا آن را برآورده سازد.

    struct پیاده‌سازی یک نسخه خاص:

    interface IValidator<T> {
        fn validate(&const self, value: T) bool;
    }
    
    struct AgeValidator : IValidator<int> {
        pub fn validate(&const self, val: int) bool {
            return val >= 18;
        }
    }
    

    یک struct جنریک که یک رابط جنریک را پیاده‌سازی می‌کند:

    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;
        }
    }
    

    جداسازی آرگومان‌های نوع

    هنگام فراخوانی یک متد جنریک روی یک نوع جنریک، آرگومان‌های نوع بین سازنده نوع (type constructor) و فراخوانی متد (method call) تقسیم می‌شوند.

    1. پارامترهای نوع: متعلق به struct/enum/union هستند و پس از نام نوع ارائه می‌شوند.
    2. پارامترهای متد: متعلق به متد خاص هستند و پس از نام متد ارائه می‌شوند.
    struct InfixCalc<T> {
        pub x: T;
        pub y: T;
    
        pub fn new(const x: T, const y: T) Self {
            return Self { x, y };
        }
    
        // متد جنریک درون یک struct جنریک
        pub fn add_to_x<V>(&self, const val: V) {
            self->x += @cast(T, val);
        }
    
        // متد استاتیک جنریک
        pub fn static_sum<K>(const a: T, const b: K) T {
            return a + @cast(T, b);
        }
    }
    
    pub fn main() {
        var calc = InfixCalc.new(5, 7);
    
        // ارائه آرگومان نوع فقط برای متد
        // اگر به نمونه آرگومان نوع اضافه کنید، منجر به خطای زمان کامپایل می‌شود
        calc.add_to_x<uint32>(1);
        calc.add_to_x<uint64>(2);
    
        printf("%d\n", calc.x);
    
        // برای فراخوانی‌های استاتیک، ممکن است نیاز به ارائه هر دو داشته باشید:
        // InfixCalc<int64> نوع را مشخص می‌کند
        // static_sum<uint> تخصصی‌سازی متد را مشخص می‌کند
        const result = InfixCalc<int64>.static_sum<uint>(10, 20);
        printf("%d\n", result);
    
        // همچنین می‌توانید اجازه دهید کامپایلر آن را استنتاج کند:
        const result = InfixCalc.static_sum(10, 20);
        printf("%d\n", result);
    }
    

    این تفکیک واضح اطمینان می‌دهد که کامپایلر دقیقاً می‌داند در هر لحظه کدام بخش از سلسله‌مراتب جنریک را تخصصی‌سازی می‌کنید.