Rust: الأنواع العامة في لغة Rust: البرمجة المعلمة بالأنواع

آخر تحديث: 2026-08-26

تُعرف «الأنواع العامة» بأنها «البرمجة المعلمة على مستوى الأنواع» — أي تمرير الأنواع كمعلمات بحيث يمكن تطبيق نفس الكود على أنواع متعددة دون الحاجة إلى إعادة كتابة الكود لكل نوع على حدة.

إذا كانت الدالة «تُجرد القيم لتصبح معلمات»، فإن الأنواع العامة «تُجرد الأنواع لتصبح معلمات» أيضًا. الأمر يشبه عندما تطلب طعامًا جاهزًا وتقول: «أريد حصة من الأرز»، دون تحديد ما إذا كنت تريده أرزًا مقليًّا أم أرزًا مع طبق آخر — فأنت تقرر ذلك بمجرد وصولك إلى المطعم.


1. ما ستتعلمه



2. الرسوم التخطيطية المفاهيمية

يوضح الرسم التخطيطي التالي لـ «Mermaid» الآلية التي يتم من خلالها استبدال معلمة النوع العام T بنوع محدد خلال عملية التحويل إلى أحادي الشكل في وقت التحويل البرمجي:

100%
graph LR
    A["Generic Functions<br/>fn identity&lt;T&gt;(x: T) -> T"] --> B["Compile-Time Singletonization<br/>Monomorphization"]
    B --> C["T → i32<br/>fn identity_i32(x: i32) -> i32"]
    B --> D["T → String<br/>fn identity_string(x: String) -> String"]
    B --> E["T → f64<br/>fn identity_f64(x: f64) -> f64"]
    C --> F["Call identity(42)"]
    D --> G["Call identity(&#34;hello&#34;.to_string())"]
    E --> H["Call identity(3.14)"]


3. قصة حاوية متعددة الاستخدامات

(1) المشكلة: كتابة كود متكرر لكل نوع

تقوم «لونا» (Luna) بتطوير مجموعة أدوات، وتحتاج إلى دالة لـ«إيجاد القيمة القصوى».

في البداية، كتبت واحدة لكل نوع:

RUST
fn max_i32(a: i32, b: i32) -> i32 {
    if a > b { a } else { b }
}

fn max_f64(a: f64, b: f64) -> f64 {
    if a > b { a } else { b }
}

fn max_str(a: &str, b: &str) -> &str {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", max_i32(3, 7));     // 7
    println!("{}", max_f64(2.5, 1.8)); // 2.5
    println!("{}", max_str("apple", "banana")); // banana
}

وبصرف النظر عن اختلاف أنواعها، فإن المنطق الذي تعمل به هذه الوظائف الثلاث هو نفسه تمامًا. ولو كان هناك أيضًا أنواع مثل u32 وu64 وchar وما إلى ذلك، لكان عليك نسخ الكود لكل نوع إضافي. وهذا ما يُعرف بـ«البرمجة بالنسخ واللصق» — وهي طريقة ليست أنيقة ولا يسهل صيانتها.

(2) نهج لغة «Rust» في التعامل مع العناصر العامة

RUST
fn max<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", max(3, 7));              // 7
    println!("{}", max(2.5, 1.8));          // 2.5
    println!("{}", max("apple", "banana")); // banana
}

تحل الدالة العامة محل ثلاث دوال لأنواع محددة — ويقوم المُجمِّع تلقائيًّا بإنشاء كود مُخصَّص لكل نوع يتم استخدامه فعليًّا (الأحادية الشكل). فأنت تكتب دالة واحدة، ويقوم المُجمِّع بتوسيعها إلى العديد من الدوال نيابةً عنك.



4. المفاهيم الأساسية

(1) النظام العام

100%
graph TB
    A[Generics Generics] --> B[Generic Functions]
    A --> C[Generic Structures]
    A --> D[Generic Enumerations]
    A --> E[Generic Methods]

    B --> F["fn identity<T>(x: T) -> T"]
    C --> G["struct Point<T> { x: T, y: T }"]
    D --> H["Option<T>, Result<T, E>"]
    E --> I["impl<T> Point<T> { fn x(&self) -> &T }"]

    A --> J[Singleton Monomorphization]
    J --> K["At compile time:T → i32, f64, String ..."]
    J --> L["Generate separate code for each type"]

(2) الأنواع العامة مقابل الأنواع المحددة مقابل التوزيع الديناميكي

السمة النوع المحدد (غير العام) العام (أحادي الشكل) التوزيع الديناميكي (السمة الديناميكية)
تكرار الكود كتابة نسخة واحدة لكل نوع التوسيع التلقائي بواسطة المُجمِّع نسخة واحدة من الكود، يتم توزيعها في وقت التشغيل
الأداء الأفضل الأفضل (بدون عبء الوظائف الافتراضية) مع عبء الوظائف الافتراضية
وقت التجميع طويل (الكثير من التعليمات البرمجية المكتوبة يدويًّا) طويل نسبيًّا (العديد من عمليات التوسيع) قصير
حجم الملفات الثنائية كبير متوسط (نسخة واحدة من كل نوع) صغير
المرونة ضعيفة تُحدد في وقت التحويل البرمجي تُحدد في وقت التشغيل

(3) التعدادات العامة الشائعة

التعداد التعريف الغرض
Option<T> enum Option<T> { Some(T), None } القيم التي قد تكون فارغة
Result<T, E> enum Result<T, E> { Ok(T), Err(E) } العمليات التي قد تفشل
Vec<T> struct Vec<T> { ... } المصفوفات الديناميكية
HashMap<K, V> struct HashMap<K, V> { ... } التعيين بين المفتاح والقيمة

(4) مقارنة بين طرق القيود العامة

نوع القيد الصيغة حالات الاستخدام مثال
القيود المضمنة fn foo<T: Trait>(x: T) القيود الفردية البسيطة fn max<T: PartialOrd>(a: T, b: T)
قيود متعددة + fn foo<T: Trait1 + Trait2>(x: T) قيود متعددة fn print<T: Display + Clone>(x: T)
جملة WHERE fn foo<T>(x: T) where T: Trait القيود المعقدة، المعلمات متعددة الأنواع where T: Display + Clone, U: Debug
سمة impl fn foo(x: impl Trait) الترميز المختصر (تسهيلات لغوية) fn plug(d: &impl USBDevice)


5. أمثلة عامة

(1) ▶ المثال:دالة عامة — إيجاد القيمة القصوى في مصفوفة (مستوى الصعوبة ⭐)

RUST
// ============================================
// Generic Functions:Applies to any comparable type
// ============================================

// PartialOrd Constraint Assurance T Supports comparison operations
fn find_max<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
    let mut max = &list[0];
    for item in list.iter() {
        if item > max {
            max = item;
        }
    }
    max
}

fn main() {
    let numbers = vec![3, 7, 1, 9, 4];
    println!("i32 Maximum value: {}", find_max(&numbers));

    let floats = vec![2.5, 1.8, 3.14, 0.99];
    println!("f64 Maximum value: {}", find_max(&floats));

    let strings = vec!["apple", "banana", "cherry", "date"];
    println!("&str Maximum value: {}", find_max(&strings));

    // The Same Function,Three Types,Compiler Auto-Expansion
}

الناتج:

TEXT 📖 للعرض فقط
i32 Maximum value: 9
f64 Maximum value: 3.14
&str Maximum value: date

find_max<T> هي دالة عامة، وT هي معلمة نوع، و<T: std::cmp::PartialOrd> هي قيد سمة — بمعنى «يجب أن يكون T نوعًا قابلًا للمقارنة». وعند استدعاء الدالة، يستنتج المُترجم تلقائيًّا T بناءً على نوع الوسيطة الفعلي.


(2) ▶ المثال:بنية عامة — نظام إحداثيات النقاط (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Generic Structures:Point Can store coordinates of any type
// ============================================

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

// Implementation Methods for Generic Structures
impl<T> Point<T> {
    // Back x Citation
    fn x(&self) -> &T {
        &self.x
    }

    // Back y Citation
    fn y(&self) -> &T {
        &self.y
    }
}

// For Point of a specific type f64, implement additional methods
impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    let int_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 3.0, y: 4.0 };
    let string_point = Point {
        x: "left",
        y: "right",
    };

    println!("int_point: {:?}", int_point);
    println!("float_point x: {}", float_point.x());
    println!("string_point: {:?}", string_point);

    // Only `Point<f64>` has the distance_from_origin method
    println!("Distance from the origin: {:.2}", float_point.distance_from_origin());

    // Compilation Error: int_point is Point<i32>, no such method
    // println!("{}", int_point.distance_from_origin());
}

الناتج:

TEXT 📖 للعرض فقط
int_point: Point { x: 5, y: 10 }
float_point x: 3.0
string_point: Point { x: "left", y: "right" }
Distance from the origin: 5.00

Point<T> هي بنية عامة؛ أما x وy فهما من نفس النوع (كلاهما من نوع T). تُنفذ impl<T> Point<T> طرقًا مشتركة لجميع مثيلات T. أما impl Point<f64> فتُنفذ طرقًا محددة لأنواع معينة فقط — وهذه إحدى المزايا الرئيسية للأنواع العامة.


(3) ▶ المثال:التعدادات العامة — استخدام «Option» و«Result» في الممارسة العملية (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Generic Enumerations: Option<T> and Result<T, E> Usage
// ============================================

// Custom Result Style Enumeration
#[derive(Debug)]
enum MyResult<T, E> {
    Success(T),
    Failure(E),
}

// Division Function:Back Result Style
fn safe_divide<T>(a: T, b: T) -> MyResult<T, String>
where
    T: std::ops::Div<Output = T> + std::cmp::PartialEq + From<u8> + Copy,
{
    if b == 0.into() {
        MyResult::Failure("Division by zero".to_string())
    } else {
        MyResult::Success(a / b)
    }
}

// Using the standard library Option<T>
fn find_in_vector<T: PartialEq>(vec: &[T], target: &T) -> Option<usize> {
    for (i, item) in vec.iter().enumerate() {
        if item == target {
            return Some(i);
        }
    }
    None
}

// Using the standard library Result<T, E>
fn parse_number(s: &str) -> Result<i32, String> {
    s.parse::<i32>().map_err(|e| format!("Parse error: {}", e))
}

fn main() {
    // Option Usage
    let numbers = vec![10, 20, 30, 40, 50];
    match find_in_vector(&numbers, &30) {
        Some(index) => println!("Found 30,Index: {}", index),
        None => println!("Not found"),
    }
    match find_in_vector(&numbers, &99) {
        Some(index) => println!("Found 99,Index: {}", index),
        None => println!("Not found 99"),
    }

    // Result Usage
    match parse_number("42") {
        Ok(n) => println!("Parsing Successful: {}", n),
        Err(e) => println!("Parsing Failed: {}", e),
    }
    match parse_number("hello") {
        Ok(n) => println!("Parsing Successful: {}", n),
        Err(e) => println!("Parsing Failed: {}", e),
    }

    // Custom MyResult Usage
    let result = safe_divide(10.0, 3.0);
    println!("Custom Result: {:?}", result);

    let result = safe_divide(10.0, 0.0);
    println!("Custom Result: {:?}", result);
}

الناتج:

TEXT 📖 للعرض فقط
Found 30,Index: 2
Not found 99
Parsing Successful: 42
Parsing Failed: Parse error: invalid digit found in string
Custom Result: Success(3.3333333333333335)
Custom Result: Failure("Division by zero")

يحتوي Option<T> على معلمة نوع واحدة فقط T (سواء كانت ذات قيمة أم لا)، في حين يحتوي Result<T, E> على معلمتي نوع (نوع النجاح ونوع الخطأ). تسمح القوائم العامة بتطبيق هذه الأنواع على أي نوع بيانات — وهذا أحد المبادئ الأساسية لتصميم المكتبة القياسية لـ Rust.


(4) ▶ المثال:معلمات الأنواع المتعددة والتوحيد (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Multiple Type Parameters + Combining Generic Methods
// ============================================

use std::fmt::Display;

// Generic struct with two type parameters
#[derive(Debug)]
struct Pair<K, V> {
    key: K,
    value: V,
}

// Implement methods for Pair<K, V>
impl<K, V> Pair<K, V> {
    fn new(key: K, value: V) -> Self {
        Pair { key, value }
    }
}

// Constrained methods: only available when both K and V implement Display
impl<K: Display, V: Display> Pair<K, V> {
    fn print(&self) {
        println!("Key: {}, Value: {}", self.key, self.value);
    }
}

// Generic Methods:Mixing Different Types of Parameters
fn mix_and_match<T, U>(a: T, b: U) -> String
where
    T: Display,
    U: Display,
{
    format!("Mixed: {} and {}", a, b)
}

fn main() {
    // Multiple Type Parameters: String and i32
    let pair1 = Pair::new("Age".to_string(), 25);
    pair1.print();

    // Multiple Type Parameters: &str and f64
    let pair2 = Pair::new("PI", 3.14159);
    pair2.print();

    // Different Types of Combinations
    let pair3 = Pair::new(100, "HTTP OK");
    // pair3.print();  // ❌ Compilation Error: i32 and &str both implement Display, but no error here.
    // In fact i32 and &str both implement Display, so you can call it
    // This is just to demonstrate the concept of constraint methods.
    pair3.print();

    // Mixing Different Types
    println!("{}", mix_and_match(42, "answer"));
    println!("{}", mix_and_match(3.14, 100));
}

الناتج:

TEXT 📖 للعرض فقط
Key: Age, Value: 25
Key: PI, Value: 3.14159
Key: 100, Value: HTTP OK
Mixed: 42 and answer
Mixed: 3.14 and 100

تسمح معلمات الأنواع المتعددة (<K, V>) للبنية (struct) بتخزين بيانات من أنواع مختلفة. وتُستخدم عبارة where لتحديد الشروط التي يجب أن تستوفيها معلمات الأنواع. وأثناء عملية التوحيد (monomorphization)، يقوم المُترجم بإنشاء كود منفصل لكل تركيبة، مثل Pair<String, i32> وPair<&str, f64>.


(5) ▶ المثال:تمرين شامل — الحاويات والخوارزميات العامة (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Generic Stack + Generic Search Algorithms
// ============================================

use std::fmt::Display;

struct Stack<T> {
    items: Vec<T>,
}

impl<T> Stack<T> {
    fn new() -> Self {
        Stack { items: Vec::new() }
    }

    fn push(&mut self, item: T) {
        self.items.push(item);
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn peek(&self) -> Option<&T> {
        self.items.last()
    }

    fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    fn len(&self) -> usize {
        self.items.len()
    }
}

impl<T: Display> Stack<T> {
    fn print_all(&self) {
        for item in &self.items {
            print!("{} ", item);
        }
        println!();
    }
}

fn find_first<T: PartialEq>(items: &[T], target: &T) -> Option<usize> {
    items.iter().position(|x| x == target)
}

fn swap_if_greater<T: PartialOrd>(a: &mut T, b: &mut T) {
    if *a > *b {
        std::mem::swap(a, b);
    }
}

fn main() {
    let mut int_stack: Stack<i32> = Stack::new();
    int_stack.push(10);
    int_stack.push(20);
    int_stack.push(30);
    println!("=== Integer Stack ===");
    println!("Stack Contents: ");
    int_stack.print_all();
    println!("Stack top: {:?}", int_stack.peek());
    println!("Pop up: {:?}", int_stack.pop());
    println!("Remaining {} element", int_stack.len());

    let mut str_stack: Stack<&str> = Stack::new();
    str_stack.push("Rust");
    str_stack.push("is");
    str_stack.push("awesome");
    println!("\n=== String Stack ===");
    str_stack.print_all();

    let nums = vec![10, 20, 30, 40, 50];
    println!("\n=== Generic Search ===");
    println!("Find 30: Index {:?}", find_first(&nums, &30));
    println!("Find 99: Index {:?}", find_first(&nums, &99));

    let words = vec!["apple", "banana", "cherry"];
    println!("Find 'banana': Index {:?}", find_first(&words, &"banana"));

    let mut x = 42;
    let mut y = 10;
    println!("\n=== Generic Swaps ===");
    println!("Before the exchange: x={}, y={}", x, y);
    swap_if_greater(&mut x, &mut y);
    println!("After the exchange: x={}, y={}", x, y);
}

الناتج:

TEXT 📖 للعرض فقط
=== Integer Stack ===
Stack Contents: 
10 20 30 
Stack top: Some(30)
Pop up: Some(30)
Remaining 2 element

=== String Stack ===
Rust is awesome 

=== Generic Search ===
Find 30: Index Some(2)
Find 99: Index None
Find 'banana': Index Some(1)

=== Generic Swaps ===
Before the exchange: x=42, y=10
After the exchange: x=10, y=42

يعمل النوع العام Stack<T> مع كل من i32 و&str؛ ويضمن القيد impl<T: Display> أن تكون الطريقة print_all متاحة فقط عندما ينفذ النوع واجهة Display؛ استخدم القيد PartialOrd لتنفيذ عامل المقارنة العام.



❓ أسئلة شائعة

س ما الفرق بين «الأنواع العامة» وBox<dyn Any>؟
ج تحدد «الأنواع العامة» الأنواع في وقت التحويل البرمجي (التوزيع الثابت)، بينما يحدد dyn Any الأنواع في وقت التشغيل (التوزيع الديناميكي). وتوفر «الأنواع العامة» أداءً أفضل لأن المُحوِّل البرمجي يُنشئ كودًا مخصصًا لكل نوع، مما يزيل العبء الإضافي الناجم عن الدوال الافتراضية. أما dyn Any فهو أكثر مرونة، حيث يمكنه التعامل مع أي نوع في وقت التشغيل، لكنه يتسبب في عبء إضافي في وقت التشغيل.
س هل يؤدي التوحيد الشكلي إلى زيادة حجم الملفات الثنائية؟
ج نعم، لكن التأثير عادةً ما يكون ضئيلًا. يتم إنشاء ملف كود منفصل لكل مجموعة من الأنواع المستخدمة فعليًّا. إذا تم استدعاء دالة عامة بعدد كبير من الأنواع المختلفة، فسيزداد حجم الملف الثنائي. ومع ذلك، يقوم مُجمِّع Rust بإجراء تحسينات، ولا يُعد «حجم الكود» عمومًا عائقًا في وحدات المعالجة المركزية الحديثة. إذا كان الحجم يمثل مشكلة، ففكر في استخدام dyn Trait بدلاً من ذلك.
س ما الفرق بين الدوال العامة والطرق العامة؟
ج الدوال العامة هي دوال مستقلة بذاتها، في حين أن الطرق العامة هي دوال مُعرَّفة على الأنواع. الدوال العامة: fn foo<T>(x: T). الطرق العامة: impl<T> MyType<T> { fn bar(&self) }. يمكن للطرق العامة الوصول إلى النوع Self، في حين أن الدوال العامة لا تستطيع ذلك.
س ما الفرق بين الكتل impl<T> وimpl؟
ج impl<T> هي طريقة التنفيذ لجميع أنواع T، في حين أن impl القياسية هي طريقة التنفيذ لأنواع محددة. impl<T> Point<T> { fn x(&self) } متاحة لجميع أنواع Point. impl Point<f64> { fn distance(&self) } متاح فقط لـ Point<f64>.
س ما الفرق بين جملة where الخاصة بحدود السمات وكتابتها مباشرةً بين أقواس زاوية؟
ج كلاهما يخدمان الغرض نفسه، لكن جملة where توفر قابلية قراءة أفضل للقيود المعقدة. fn foo<T: Display + Clone, U: Debug>(t: T, u: U) تعادل fn foo<T, U>(t: T, u: U) where T: Display + Clone, U: Debug. يُنصح باستخدام جملة where عند وجود قيود متعددة.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: اكتب دالة عامة fn echo<T>(x: T) -> T تأخذ قيمةً ما وتُرجعها كما هي. في الدالة main، استدعِها بالقيم i32 وf64 و&str، على التوالي.
  2. الصعوبة ⭐⭐: عرّف بنية عامة Container<T> تحتوي على حقل value: T. يعيد التنفيذ fn get(&self) -> &T مرجعًا إلى القيمة، بينما يقوم fn set(&mut self, val: T) بتعديل القيمة. اختبر ذلك في الدالة الرئيسية باستخدام Container<String> وContainer<i32> على التوالي.
  3. الصعوبة ⭐⭐⭐: اكتب دالة عامة fn merge_arrays<T>(a: &[T], b: &[T]) -> Vec<T> تقوم بدمج شريحتين وتُرجع كائن Vec جديدًا. يجب أن ينفذ النوع T واجهة Clone. بعد ذلك، في الدالة الرئيسية، قم بدمج شريحتين من نوع i32 وشريحتين من نوع &str، على التوالي.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%