الدرس3 / 3

الدوال

قراءة

اللي هتفهمه

  • إزاي تعرّف دالة وتمرر لها قيم وترجّع منها قيمة.
  • الفرق بين الجملة والتعبير، وده أهم فرق في الدرس ده.
  • ليه فاصلة منقوطة واحدة بتقلب الكود من شغال لمرفوض.

الشرح

الدالة بتتعرّف بـ fn، والأسماء بالعُرف snake_case:

fn main() {
    another(5, 'ح');
    println!("plus_one(5) = {}", plus_one(5));
}

fn another(value: i32, unit: char) {
    println!("{value}{unit}");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}
افتح في Playground

الخرج:


plus_one(5) = 6

تلات ملاحظات:

ترتيب التعريف ملوش أهمية. main بتنده على another رغم إنها متعرّفة تحتها. الكومبايلر بيشوف الملف كله قبل ما يترجم.

نوع كل باراميتر إجباري. مفيش استنتاج هنا. لو نسيت النوع، الكومبايلر بيوقف عند الفاصلة نفسها وبيقترح عليك تضيف نوع:

error: expected one of `:`, `@`, or `|`, found `,`
 --> src/main.rs:1:9
  |
1 | fn add(a, b) -> i32 { a + b }
  |         ^ expected one of `:`, `@`, or `|`
  |
help: if this is a parameter name, give it a type
  |
1 | fn add(a: TypeName, b) -> i32 { a + b }
  |         ++++++++++

rustc 1.97.1 (8bab26f4f 2026-07-14)

ده مقصود، مش نقص. توقيع الدالة بيبقى عقد كامل: أي حد بيقرا السطر الأول بيعرف كل حاجة عن المدخلات والمخرجات من غير ما يقرا الجسم.

نوع الإرجاع بيتكتب بعد ->، وخد بالك إنها شرطة وعلامة أكبر — مش سهم. لو محررك بيوريها سهم، ده خط بيدمج الحرفين وإنت مش هتقدر تكتبه من الكيبورد.

المرئي

السطر الأخير في الدالةقيمتهالنتيجةx + 1تعبير — بيتحوّل لقيمة، والقيمة دي هي ناتج الدالةi32✓ بيترجمexpressionx + 1;جملة — بتنفّذ وبتتخلص من الناتج، فالدالة بترجع ولا حاجة()✕ E0308statement
() اسمها الوحدة — نوع قيمته الوحيدة هي "ولا حاجة". دالة معلنة إنها بترجع i32 وبترجع() مش بتترجم، وده معنى رسالةE0308.

الكود

الجملة والتعبير

ده الفرق اللي كل حاجة في الدرس بتدور حواليه:

  • الجملة بتنفّذ حاجة ومش بترجّع قيمة.
  • التعبير بيتحوّل لقيمة.

في Rust معظم الحاجات تعبيرات، وده مش صحيح في C أو Java. الكتلة بين قوسين {} نفسها تعبير:

fn main() {
    let y = {
        let x = 3;
        x + 1
    };
    println!("y = {y}");
}
افتح في Playground

بيطبع y = 4. الكتلة اتحوّلت لقيمة 4، والقيمة دي اترمت في y.

خد بالك إن x + 1 مفيش بعدها فاصلة منقوطة. ده مش إهمال — ده السطر اللي بيحدد قيمة الكتلة. لو حطيت فاصلة منقوطة، الكتلة بتبقى قيمتها ()، وy بقى مالوش قيمة مفيدة.

وif كمان تعبير:

fn main() {
    let y = 4;
    let big = if y > 3 { "كبير" } else { "صغير" };
    println!("{big}");
}
افتح في Playground

مفيش حاجة زي condition ? a : b في Rust، لأن if نفسه بيعمل الشغل ده.

الإرجاع

في Rust بترجّع القيمة بإنك تسيب آخر تعبير من غير فاصلة منقوطة:

fn plus_one(x: i32) -> i32 {
    x + 1
}

وفيه return للخروج المبكر:

fn abs_diff(a: i32, b: i32) -> i32 {
    if a >= b {
        return a - b;
    }
    b - a
}

fn main() {
    println!("{} {}", abs_diff(9, 4), abs_diff(4, 9));
}
افتح في Playground

بيطبع 5 5. return هنا للخروج المبكر، والسطر الأخير هو الإرجاع العادي.

ليه بيشتغل كده

خد الدالة دي، وحط فاصلة منقوطة واحدة في آخر سطرها:

fn plus_one(x: i32) -> i32 {
    x + 1;
}

الكود مبقاش بيترجم. الرسالة بتشرح كل حاجة، والسطر المهم فيها هو implicitly returns () — يعني الدالة بقت بترجع “ولا حاجة” بدل i32، والكومبايلر بيقولك حرفياً إن الحل إنك تشيل الفاصلة المنقوطة:

error[E0308]: mismatched types
 --> src/main.rs:1:24
  |
1 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     x + 1;
  |          - help: remove this semicolon to return this value

rustc 1.97.1 (8bab26f4f 2026-07-14)

الفاصلة المنقوطة بتحوّل التعبير لجملة. x + 1 تعبير قيمته i32؛ x + 1; جملة بتتخلص من الناتج. الدالة بقى مبقاش ليها سطر أخير بيرجّع حاجة، فهي بترجع () — والنوع المعلَن i32.

نفس المنطق بيفسر ليه ده مرفوض:

let x = (let y = 6);

let y = 6 جملة، والجمل ملهاش قيمة، فمفيش حاجة تترمي في x. الكومبايلر بيقول كده مباشرة:

error: expected expression, found `let` statement
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^
  |
  = note: only supported directly in conditions of `if` and `while` expressions

rustc 1.97.1 (8bab26f4f 2026-07-14)

عشان كده x = y = 5 اللي بتشتغل في C مش موجودة هنا.

والنتيجة الأهم: لأن if والكتل تعبيرات، مش محتاج تعرّف متغير فاضي وتملاه بعدين. القيمة بتتحسب في مكان واحد وبتترمي في اسم واحد، وده بيخلّي المتغير غير قابل للتغيير — وشفنا في درس المتغيرات إن ده اللي الكومبايلر بيحب يشتغل عليه.

تمرين

تلات دوال، كل واحدة فيها غلطة واحدة. صلّحها:

fn a(x: i32) -> i32 {
    x * 2;
}

fn b(x) -> i32 {
    x + 1
}

fn c(x: i32) -> i32 {
    let y = { x + 1; };
    y
}
الحل

a — فاصلة منقوطة زيادة في السطر الأخير. الدالة بترجع () والمعلَن i32. شيلها:

fn a(x: i32) -> i32 {
    x * 2
}

b — الباراميتر من غير نوع. نوع الباراميتر إجباري دايماً:

fn b(x: i32) -> i32 {
    x + 1
}

c — نفس غلطة a بس مخبّية جوه كتلة. { x + 1; } قيمتها ()، فـ y نوعه ()، والدالة بترجّعه. شيل الفاصلة المنقوطة من جوه الكتلة:

fn c(x: i32) -> i32 {
    let y = { x + 1 };
    y
}

التلات غلطات دي في الحقيقة غلطة واحدة: خلط بين الجملة والتعبير. لما تشوف expected i32, found () في المستقبل، أول حاجة تدوّر عليها هي فاصلة منقوطة زيادة.

النقاش

لو عندك سؤال أو لقيت غلطة في الصفحة دي، اكتبها فيالنقاشات على GitHub. كل صفحة ليها موضوعها الخاص.