Rust: دوال لغة Rust ونطاقها
آخر تحديث: 2026-08-26
تُعتبر الدوال عناصر من الدرجة الأولى في لغة Rust — حيث تُشكل أنواع معلماتها وقيمها المرجعة خط الدفاع الأول للمترجم.
تُعد الدوال اللبنات الأساسية للكود. تشبه صيغة الدوال في لغة Rust تلك المستخدمة في لغات عائلة C، لكنها تتميز بأسلوب فريد فيما يتعلق بـ مطابقة أنماط الوسيطات وقيم إرجاع التعبيرات.
1. ما ستتعلمه
- استخدم
fnلتعريف دالة وتحديد أنواع المعلمات والقيم المرجعة - ثلاث طرق لتمرير المعلمات: التمرير بالقيمة، والتمرير بالمرجع، والمرجع القابل للتغيير
- قيم إرجاع الدوال والعودة المبكرة
- نطاق الكتلة وتظليل المتغيرات
- تعمل كوحدات أساسية لتنظيم الكود
- مقدمة إلى مؤشرات الدوال والدوال ذات الترتيب الأعلى
2. قصة مقهى
(1) التحدي: لا توجد طريقة موحدة لتحضير القهوة
افتتحت ليزا مقهى، وأصبح العمل يزدهر أكثر فأكثر، لكن ظهرت بعض المشاكل أيضًا:
- كل باريستا يُعدّ مشروب اللاتيه بطريقة مختلفة
- يضيف البعض الحليب أولاً، ثم القهوة؛ بينما يضيف آخرون القهوة أولاً، ثم الحليب.
- اشتكى أحد العملاء قائلاً: «نفس اللاتيه يختلف مذاقه في كل مرة».
- تريد ليزا كتابة وصفة موحدة — تمامًا مثل الدالة: تُدخل كميات الحليب والقهوة، فتُنتج لاتيه بنكهة ثابتة.
«سيكون من الرائع لو كان تحضير القهوة متسقًا تمامًا مثل استدعاء دالة: فالمدخلات نفسها تنتج دائمًا نفس النتائج.»
(2) نهج التعامل مع دوال Rust
// How to Make a Latte"Function":Input Parameters,Brew Coffee
fn make_latte(milk_ml: i32, coffee_ml: i32) -> String {
let result = format!("Latte Complete:{}ml Milk + {}ml Coffee,Fragrant and Delicious!", milk_ml, coffee_ml);
result // No semicolons——Expression Return Value
}
fn main() {
let my_coffee = make_latte(200, 30);
println!("{}", my_coffee);
let another = make_latte(150, 45);
println!("{}", another);
}
الدوال تشبه وصفات القهوة: المعلمات هي المكونات (كمية الحليب، كمية القهوة)، والقيمة المرجعة هي المنتج النهائي. نفس المدخلات تضمن نفس المخرجات — وهذا هو الحتمية في الدوال.
3. تعريفات الدوال
(1) قواعد النحو الأساسية
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
// Function Body
return_value // Expression returns(No semicolons)
}
graph TB
A[fn Function Name] --> B[(List of Parameters)]
A --> C[-> Return Type]
A --> D[{ Function Body }]
B --> E[Each parameter: name: Type]
D --> F[Final expression = Return Value]
D --> G[return Keywords = Return Early]
(2) الأشكال المختلفة للمعلمات وقيم الإرجاع
| النموذج | الصيغة | المثال |
|---|---|---|
| لا توجد معلمات، ولا قيمة إرجاع | fn foo() |
fn greet() { println!("hi"); } |
| تستقبل معلمات، ولا تُرجع أي قيمة | fn foo(x: i32) |
fn show(n: i32) { println!("{}", n); } |
| مع معلمات وقيمة إرجاع | fn foo(x: i32) -> i32 |
fn double(x: i32) -> i32 { x * 2 } |
| قيم إرجاع متعددة (توبول) | fn foo() -> (i32, bool) |
fn stats() -> (i32, bool) { (42, true) } |
| العودة | return value; |
if x < 0 { return 0; } |
القاعدة الأساسية: التعبير الأخير في نص الدالة هو قيمة الإرجاع (بدون فاصلة منقوطة). استخدم الكلمة الرئيسية
returnللعودة المبكرة.
(3) طرق تمرير المعلمات
| طريقة التمرير | الصيغة | تغيير الملكية | السيناريوهات التي تنطبق عليها |
|---|---|---|---|
| التمرير بالقيمة | fn foo(s: String) |
تنتقل الملكية إلى الدالة | يتعين على الدالة استهلاك القيمة |
| التمرير بواسطة مرجع ثابت | fn foo(s: &String) |
الاستعارة، مع بقاء الملكية دون تغيير | للقراءة فقط |
| التمرير بالرجوع | fn foo(s: &mut String) |
الاستعارة، مع بقاء الملكية دون تغيير | يجب تعديل البيانات |
(4) مقارنة قيم الإرجاع
| الطريقة | الصيغة | تغيير الملكية | مثال |
|---|---|---|---|
| عوائد التعبير | عدم وجود فاصلة منقوطة في نهاية السطر | نقل الملكية | fn f() -> String { s } |
| العودة | return value; |
نقل الملكية | if err { return None; } |
| إرجاع مجموعات متعددة | -> (T1, T2) |
عمليات نقل ملكية متعددة | fn f() -> (i32, bool) |
| معلمة الإخراج | &mut T |
لا يوجد نقل للملكية | fn fill(buf: &mut Vec<i32>) |
4. النطاق والتظليل
(1) نطاق الكتلة
يتم تعريف النطاق في لغة Rust وفقًا لـ {}. ولكل كتلة نطاقها الخاص بها:
fn main() {
let x = 10; // Outer Scope
{
let y = 20; // Inner Scope,y Available only here
println!("Inside: x={}, y={}", x, y); // ✅ Can access the outer layer x
}
// println!("{}", y); // ❌ Compilation Error:y Not in this scope
println!("External: x={}", x); // ✅
}
(2) التظليل المتغير
يمكن أن يُحجب تأثير المتغيرات الخارجية بواسطة متغيرات داخلية تحمل الاسم نفسه، ولا يمكن للكود الداخلي الوصول إلا إلى المتغيرات الداخلية:
fn main() {
let x = 1; // Outer layer x = 1
{
let x = 2; // Inner layer x Covered the outer layer x
println!("Inside x = {}", x); // Output: Inside x = 2
}
println!("External x = {}", x); // Output: External x = 1
}
(3) نطاق معلمات الدالة
تكون معلمات الدالة مرئية في جميع أنحاء نص الدالة بأكمله:
fn foo(x: i32) {
// x Available throughout the entire function body
let y = x + 1;
println!("x={}, y={}", x, y);
} // End of function, x and y are both destroyed
5. أمثلة على الوظائف
(1) ▶ المثال:توقيعات الدوال المتنوعة (الصعوبة ⭐)
// ============================================
// Functions with Different Combinations of Parameters and Return Types
// ============================================
// No parameters, no return value
fn print_separator() {
println!("-------------------");
}
// Single-parameter functions with return values
fn square(x: i32) -> i32 {
x * x // No semicolons,Expression returns
}
// Multi-parameter
fn area(width: u32, height: u32) -> u32 {
width * height
}
// Return multiple values(Tuple)
fn min_max(a: i32, b: i32) -> (i32, i32) {
if a < b {
(a, b) // Tuples as Return Values
} else {
(b, a)
}
}
fn main() {
print_separator();
println!("5 the square of: {}", square(5));
println!("Area 10x20: {}", area(10, 20));
let (min, max) = min_max(100, 50);
println!("min={}, max={}", min, max);
}
الناتج:
-------------------
5 the square of: 25
Area 10x20: 200
min=50, max=100
min_maxتُرجع مجموعة؛ وعند استدعائها، يتم تفكيكها وتعيين قيمها باستخدامlet (min, max). وهذه هي الطريقة القياسية لـ«إرجاع قيم متعددة» في لغة Rust.
(2) ▶ المثال:العودة المبكرة ووضع الحراسة (مستوى الصعوبة ⭐⭐)
// ============================================
// Return Early:Inspection Criteria,If you're not satisfied, leave.
// ============================================
fn safe_divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
println!("Error:The divisor cannot be 0");
return f64::NAN; // Return Early NaN(Not a Number)
}
a / b // Under normal circumstances,Expression returns
}
fn get_grade(score: i32) -> &'static str {
if score < 0 || score > 100 {
return "Invalid Fractions";
}
if score >= 90 { return "A"; }
if score >= 80 { return "B"; }
if score >= 70 { return "C"; }
if score >= 60 { return "D"; }
"F"
}
fn main() {
println!("10 / 3 = {}", safe_divide(10.0, 3.0));
println!("10 / 0 = {}", safe_divide(10.0, 0.0));
println!("Fractions 85: {}", get_grade(85));
println!("Fractions -5: {}", get_grade(-5));
}
الناتج:
10 / 3 = 3.3333333333333335
Error:The divisor cannot be 0
10 / 0 = NaN
Fractions 85: B
Fractions -5: Invalid Fractions
يُستخدم «العودة المبكرة» (
return) بشكل شائع في نمط «التحقق أولاً، ثم التنفيذ». ومن خلال معالجة حالات الخطأ والحالات الاستثنائية في البداية، تسير المنطقية اللاحقة بسلاسة ضمن «منطقة آمنة».
(3) ▶ المثال:التعبيرات الكتلية واختبار نطاقها (مستوى الصعوبة ⭐⭐)
// ============================================
// Scope and Return Values of Block Expressions
// ============================================
fn main() {
let outer = "Outer-scope variables".to_string();
let inner_result = {
let inner = "Nested variables".to_string();
println!("Accessible internally: {}", outer); // ✅ The inner layer can access the outer layer
// Shading Variables with the Same Name
let outer = 42;
println!("After masking outer: {}", outer); // 42,Not a string
inner.len() // Block expression return values:inner length
}; // inner Destroyed here
println!("The value returned by the block: {}", inner_result);
println!("External outer: {}", outer); // ✅ Outer layer outer Still
// println!("{}", inner); // ❌ Compilation Error:inner Not in this scope
}
الناتج:
Accessible internally: Outer-scope variables
After masking outer: 42
The value returned by the block: 8
External outer: Outer-scope variables
قواعد نطاق الكتل: يمكن للكتل الداخلية الوصول إلى المتغيرات الخارجية، لكن الكتل الخارجية لا يمكنها الوصول إلى المتغيرات الداخلية. ولا يؤدي التظليل سوى إلى «إخفاء» المتغيرات الخارجية مؤقتًا؛ وبمجرد الخروج من الكتلة الداخلية، تعود المتغيرات الخارجية إلى حالتها الطبيعية.
(4) ▶ المثال:مؤشرات الدوال والدوال ذات الترتيب الأعلى (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Passing Functions as Arguments——An Introduction to Higher-Order Functions
// ============================================
type MathOp = fn(i32, i32) -> i32;
fn add(a: i32, b: i32) -> i32 { a + b }
fn sub(a: i32, b: i32) -> i32 { a - b }
fn mul(a: i32, b: i32) -> i32 { a * b }
fn calculate(op: MathOp, a: i32, b: i32) -> i32 {
op(a, b)
}
fn apply_twice(f: fn(i32) -> i32, x: i32) -> i32 {
f(f(x))
}
fn main() {
let result1 = calculate(add, 10, 5);
let result2 = calculate(sub, 10, 5);
let result3 = calculate(mul, 10, 5);
println!("10 + 5 = {}", result1);
println!("10 - 5 = {}", result2);
println!("10 * 5 = {}", result3);
let double = |x: i32| x * 2;
let result4 = apply_twice(double, 3);
println!("double(double(3)) = {}", result4);
let increment = |x: i32| x + 1;
let result5 = apply_twice(increment, 5);
println!("increment(increment(5)) = {}", result5);
let alice_points = 80;
let bonus = |base: i32| -> i32 { base + 10 };
println!("Alice After the bonus points are added: {}", bonus(alice_points));
}
الناتج:
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
double(double(3)) = 12
increment(increment(5)) = 7
Alice After the bonus points are added: 90
يمكن تمرير كل من مؤشرات الدوال (من النوع
fn) والإغلاقات (من النوع|x| x * 2) كمعلمات إلى الدوال ذات الترتيب الأعلى. تشير مؤشرات الدوال إلى عناوين التعليمات البرمجية التي يتم تحديدها في وقت التحويل البرمجي، بينما يمكن للإغلاقات التقاط متغيرات البيئة.
▶ Example:Comprehensive Exercise—Processing Student Grades (Difficulty ⭐⭐⭐)
// ============================================
// Comprehensive Example:Function,Combining Scope and Shading
// ============================================
fn average(scores: &[i32]) -> f64 {
if scores.is_empty() {
return 0.0;
}
let sum: i32 = scores.iter().sum();
sum as f64 / scores.len() as f64
}
fn classify(score: i32) -> &'static str {
match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
}
}
fn report(names: &[&str], scores: &[i32]) {
let avg = average(scores);
println!("Class Average Score: {:.1}", avg);
for (i, name) in names.iter().enumerate() {
let score = scores[i];
let grade = classify(score);
let status = if score >= 60 { "Through" } else { "Failed" };
println!("{}: {} pts, Level {}, {}", name, score, grade, status);
}
let passing = scores.iter().filter(|&&s| s >= 60).count();
println!("Number of people who passed: {}/{}", passing, scores.len());
}
fn main() {
let names = ["Alice", "Bob", "Charlie", "David"];
let scores = [95, 72, 58, 88];
println!("=== Report Card ===");
report(&names, &scores);
println!("\n--- Scope Demonstration ---");
let result = {
let scores = [100, 90, 80];
let avg = average(&scores);
format!("Average of the top three: {:.1}", avg)
};
println!("{}", result);
let avg = "Outer layer avg";
println!("Outer-scope variables: {}", avg);
}
الناتج:
=== Report Card ===
Class Average Score: 78.2
Alice: 95 pts, Level A, Through
Bob: 72 pts, Level C, Through
Charlie: 58 pts, Level F, Failed
David: 88 pts, Level B, Through
Number of people who passed: 3/4
--- Scope Demonstration ---
Average of the top three: 90.0
Outer-scope variables: Outer layer avg
يجمع هذا المثال بين استخدام تعريفات الدوال، ومعلمات الشرائح، وتصنيف
match، وطرق المكرر، وإخفاء المتغيرات في نطاق الكتلة. ويوفر استخدام شريحة&[T]كمعلمة للدالة أقصى درجات المرونة، كما أن إخفاء متغير يحمل الاسم نفسه داخل نطاق الكتلة لا يؤثر على النطاق الخارجي.
❓ أسئلة شائعة
return والعودة من خلال تعبير؟return للعودة المبكرة، بينما تُستخدم العودة من خلال تعبير في نهاية الدالة.📖 ملخص
- تُعرَّف الدوال باستخدام الكلمة الرئيسية
fn، ويجب تحديد أنواع المعلمات. - إذا لم تنتهِ التعبير الأخير في نص الدالة بفاصلة منقوطة، فإنه يُعتبر القيمة المُرجعة.
returnالكلمة المفتاحية المستخدمة في حالة الإرجاع المبكر{}تحدد الكتل نطاقها؛ ويمكن للكتل الداخلية الوصول إلى المتغيرات الخارجية، ولكن لا يمكن للمتغيرات الخارجية الوصول إلى المتغيرات الداخلية.- التظليل المتغير يسمح للنطاقات الداخلية بإعلان متغيرات تحمل الاسم نفسه، مما يؤدي إلى تجاوز المتغيرات الموجودة في النطاقات الخارجية مؤقتًا.
- تكون معلمات الدالة مرئية في جميع أنحاء نص الدالة بأكمله
📝 تمارين
- الصعوبة ⭐: اكتب دالة
fn is_even(n: i32) -> boolتحدد ما إذا كان عدد صحيح زوجيًا أم لا، ثم اكتبmainلاستدعاء هذه الدالة وطباعة فئة كل عدد من 1 إلى 10. - الصعوبة ⭐⭐: اكتب دالة
fn max_of_three(a: i32, b: i32, c: i32) -> i32تُرجع أكبر قيمة بين ثلاثة أعداد. (لا يجوز استخدام دالة.max()الموجودة في المكتبة القياسية؛ اكتب منطق المقارنة الخاص بك.) - الصعوبة ⭐⭐⭐: اكتب دالة
fn calculator(op: char, a: f64, b: f64) -> f64تقوم بإجراء العملية المقابلة بناءً على المعلماتop('+''-''*''/')، وتُرجعf64::NANعندما يكون المقسوم عليه 0. اختبر هذه العمليات الأربع فيmain.