عبارات شرطی

    عبارات شرطی به شما امکان می‌دهند بلوک‌های مختلف کد را بسته به اینکه یک شرط true یا false ارزیابی شود، اجرا کنید. شرایط باید به یک مقدار bool ارزیابی شوند.

    if (condition) {
        // executes if condition is true
    } else {
        // executes if condition is false
    }
    

    شرط‌های زنجیره‌ای:

    var grade: char;
    
    if (score >= 90) {
        grade = 'A';
    } else if (score >= 75) {
        grade = 'B';
    } else {
        grade = 'C';
    }
    

    ساختارهای حلقه

    یک حلقه for امکان تکرار یک بلوک کد را چندین بار فراهم می‌کند. معمولاً از سه بخش اختیاری درون سرآیند حلقه تشکیل شده است:

    • مقداردهنده اولیه (Initializer) - یک بار قبل از شروع حلقه اجرا می‌شود (اغلب برای اعلان و مقداردهی یک شمارنده استفاده می‌شود).
    • شرط (Condition) - قبل از هر تکرار بررسی می‌شود؛ اگر false باشد، حلقه خارج می‌شود.
    • افزایش/به‌روزرسانی (Increment/Update) - پس از هر تکرار اجرا می‌شود.
    for (initializer; condition; increment) {
        // body
    }
    

    حلقه شمارنده کلاسیک

    for (var i = 0; i < 10; i++) {
        printf("%d\n", i);
    }
    

    با i که مقادیر 0 تا 9 را می‌گیرد اجرا می‌شود.

    حلقه با افزایش دستی

    for (var i = 0; i < 10;) {
        // executes while i < 10
        // you should manually increment i inside the body
        i++;
    }
    

    در اینجا، بخش افزایش حذف شده است. حلقه تا زمانی که شرط false شود ادامه می‌یابد، اما شما کنترل می‌کنید که i چه زمانی به‌روز شود.

    حلقه بدون شرط با مقداردهنده اولیه

    for (var attempts = 0;) {
        if (attempts > 5) {
            break;
        }
    
        try_connect();
        attempts++;
    }
    

    با عدم ارائه شرط، حلقه بی‌نهایت است مگر اینکه با break یا return خارج شود.

    حلقه بی‌نهایت خالص

    for {
        // infinite loop with no initializer, no condition, no increment
        // must be exited with break or return
    }
    

    این فرم هیچ مقداردهنده اولیه، شرط و به‌روزرسانی ندارد. باید به صورت صریح خاتمه یابد.

    دستور while

    یک حلقه while یک بلوک کد را تا زمانی که یک شرط true ارزیابی شود، به طور مکرر اجرا می‌کند. شرط قبل از هر تکرار بررسی می‌شود. اگر شرط در ابتدا false باشد، بدنه حلقه هرگز اجرا نمی‌شود.

    while (is_running) {
        tick();
    }
    
    var attempts = 0;
    
    while (true) {
        printf("attempt %d\n", attempts);
        attempts++;
    
        if (attempts >= max_retries) {
            break;
        }
    }
    

    دستور Switch

    دستور switch جریان کنترل ساختاریافته‌ای را بر اساس مقدار یک عبارت فراهم می‌کند. از انواع enum با تخریب‌سازی (destructuring)، تطبیق مبتنی بر مقدار و یک شاخه default اختیاری پشتیبانی می‌کند.

    فرم کلی به صورت زیر است:

    فرم پایه

    switch (expression) {
        case pattern => {
            // body
        }
        default => {
            // optional fallback
        }
    }
    

    هر case با استفاده از => معرفی می‌شود و بلوک مرتبط خود را زمانی که الگو مطابقت دارد اجرا می‌کند.
    اجرا به case بعدی سرایت نمی‌کند (fall through)؛ دقیقاً یک شاخه مطابق اجرا می‌شود.

    Switch روی Enumها

    هنگام switch روی مقادیر enum، هر case یک variant خاص را مطابقت می‌دهد.
    Variantهای بدون payload با نام مطابقت می‌کنند، در حالی که variantهای دارای داده مرتبط می‌توانند فیلدهای خود را مستقیماً تخریب (destructure) کنند.

    enum Color {
        Red,
        Green = "green!",
        Custom(uint, uint, uint)
    }
    
    pub fn main() {
        const color = Color.Green;
    
        switch (color) {
            case .Red => {
                printf("red\n");
            }
            case .Green(value) => {
                printf("%s\n", value);
            }
            case .Custom(r, g, b) => {
                printf("(%d, %d, %d)\n", r, g, b);
            }
        }
    }
    

    switch جامع (exhaustive) است: همه variantهای ممکن Color پوشش داده شده‌اند.

    Switch روی Enum بدون Payload

    enum Color {
        Red,
        Purple,
        Black
    }
    
    pub fn main() {
        const color = Color.Black;
    
        switch (color) {
            case .Red => {
                printf("red\n");
            }
            case .Purple => {
                printf("purple\n");
            }
            case .Black => {
                printf("black\n");
            }
        }
    }
    

    هر case یک variant مشخص از enum را مطابقت می‌دهد. از آنجا که همه variantها پوشش داده شده‌اند، نیازی به شاخه default نیست.

    تخریب‌سازی مقادیر برچسب‌دار

    انواع enum معمولاً به عنوان اتحاد برچسب‌دار (tagged unions) استفاده می‌شوند. دستور switch امکان استخراج ایمن مقدار ذخیره‌شده بر اساس variant فعال را فراهم می‌کند.

    enum Value {
        Int(int64),
        Text(char*)
    }
    
    pub fn main() {
        const value = Value.Text("Cyrus!");
    
        switch (value) {
            case .Int(number) => {
                printf("int_value(%d)\n", number);
            }
            case .Text(text) => {
                printf("text_value(%s)\n", text);
            }
        }
    }
    

    در اینجا، متغیر مقید (number, text) فقط درون بلوک case مربوطه خود قابل دسترسی است.

    تطبیق چندین Variant از Enum

    وقتی یک variant از enum فاقد payload است، می‌توان آن را با سایر variantهای بدون payload با استفاده از عملگر | درون یک case ترکیب کرد.

    این امکان گروه‌بندی چندین variant را تحت یک شاخه فراهم می‌کند.

    enum Color {
        Red,
        Green,
        Custom(uint, uint, uint)
    }
    
    pub fn main() {
        const color = Color.Green;
    
        switch (color) {
            case .Red | .Custom => {
                printf("primary\n");
            }
            case .Green => {
                printf("green\n");
            }
        }
    }
    

    فقط variantهای بدون payload ممکن است با | گروه‌بندی شوند. Variantهای دارای payload باید به صورت جداگانه مطابقت داده شوند تا فیلدهایشان قابل تخریب باشد. Caseهای گروه‌بندی شده به عنوان یک شاخه واحد رفتار می‌کنند؛ هیچ binding الگویی مجاز نیست.

    Switch روی مقادیر غیر Enum

    Switch همچنین می‌تواند با عبارات غیر enum مانند اعداد صحیح، رشته‌ها یا سایر مقادیر قابل مقایسه استفاده شود.

    const text = "Cyrus!";
    
    switch (text) {
        case "Cyrus!" => {
            printf("Cyrus The Great\n");
        }
        default => {
            printf("unknown\n");
        }
    }
    

    در این فرم:

    • هر case عبارت switch را با یک مقدار مشخص مقایسه می‌کند
    • Default زمانی اجرا می‌شود که هیچ case مطابقت نداشته باشد
    • تطبیق مبتنی بر مقدار است، نه مبتنی بر الگو

    بلوک‌های پرش

    کوروش از بلوک‌های پرش با استفاده از دستور goto پشتیبانی می‌کند. بلوک‌های پرش به شما امکان می‌دهند کنترل را به یک نقطه برچسب‌گذاری شده در تابع جاری منتقل کنید. اگرچه goto باید به ندرت استفاده شود، می‌تواند جریان‌های کنترل سطح پایین یا منطق شبیه به ماشین حالت را ساده کند.

    یک برچسب (label) یک شناسه است که با دو نقطه دنبال می‌شود و دستور goto اجرا را به آن برچسب منتقل می‌کند:

    pub fn main() {
    label_name:
        // code
    
    goto label_name; // jumps to the labeled block
    }
    

    برچسب‌ها باید درون همان تابع تعریف شوند.

    import std::libc{printf, exit};
    
    pub fn main() {
        var x = 0;
    
    increment: // label for incrementing
        x++;
    
    check: // label for condition check
        if (x < 5) {
            goto increment; // jump back to increment
        } else {
            printf("%d\n", x);
            exit(0);
        }
    }