0Pricing
Learn Rust Coding · درس

نقل البيانات إلى الخيوط

استخدم دوال الإغلاق move بطريقة صحيحة

نقل البيانات إلى الخيوط درس مجاني في Learn Rust Coding على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Learn Rust Coding، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

تلتقط الإغلاقات البيئة المحيطة

يمكن لإغلاق مُمرَّر إلى thread::spawn استخدام متغيرات من النطاق المحيط. ويستعيرها Rust افتراضيًا.

لكن قد يستمر خيط أُنشئ حديثًا بعد انتهاء الدالة التي أنشأته، ولذلك لا يكون الاستعارة العادية آمنة. ويرفض Rust ذلك وقت الترجمة.

مشكلة الاستعارة

إذا استعار خيط متغيرًا محليًا، فلا يستطيع المترجم إثبات أن المتغير سيظل موجودًا مدة كافية. فقد يستمر الخيط في العمل بعد إسقاط المتغير.

لا يُترجم الكود أدناه، لأن الإغلاق لا يستعير سوى data.

use std::thread;

fn main() {
    let data = vec![1, 2, 3];
    // ERROR: closure may outlive `data`
    let h = thread::spawn(|| {
        println!("{:?}", data);
    });
    h.join().unwrap();
}

الكلمة المفتاحية move

تؤدي إضافة move قبل الإغلاق إلى إجباره على تولّي ملكية المتغيرات الملتقطة. وتُنقل هذه المتغيرات إلى الخيط.

يمتلك الخيط الآن البيانات، ولذلك نضمن بقاءها صالحة طوال مدة حياة الخيط.

use std::thread;

fn main() {
    let data = vec![1, 2, 3];
    let h = thread::spawn(move || {
        println!("{:?}", data);
    });
    h.join().unwrap();
}

انتقال الملكية إلى الخارج

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

ستؤدي محاولة استخدام data في main بعد النقل إلى خطأ في الترجمة. والخيط هو المالك الوحيد الآن.

use std::thread;

fn main() {
    let data = vec![1, 2, 3];
    let h = thread::spawn(move || println!("{:?}", data));
    // println!("{:?}", data); // ERROR: value moved
    h.join().unwrap();
}

نقل الأنواع القابلة للنسخ

تُنسخ الأنواع التي تطبّق Copy، مثل الأعداد الصحيحة، بدلًا من نقلها. ويحصل الإغلاق على نسخته الخاصة.

لذلك، بعد أن يلتقط إغلاق يستخدم move عددًا، يظل بإمكانكم استخدام النسخة الأصلية في main.

use std::thread;

fn main() {
    let n = 42;
    let h = thread::spawn(move || {
        println!("thread sees {}", n);
    });
    println!("main still sees {}", n);
    h.join().unwrap();
}

Send: آمن للنقل

لا يمكن نقل نوع إلى خيط آخر إلا إذا كان يطبّق السمة Send. وتعني Send أن نقل الملكية عبر الخيوط آمن.

معظم الأنواع هي Send تلقائيًا. لكن بعض الأنواع، مثل Rc، ليست كذلك، وسيرفض المترجم نقلها.

Rc ليس Send

يُعد Rc عدّاد مراجع مخصصًا لخيط واحد. ولا يكون عدّاده محميًا من التحديثات المتزامنة، لذلك فهو ليس Send.

تفشل ترجمة محاولة نقل Rc إلى خيط. وستستخدمون Arc بدلًا منه في الدرس التالي.

use std::rc::Rc;
use std::thread;

fn main() {
    let r = Rc::new(5);
    // ERROR: `Rc<i32>` cannot be sent between threads safely
    let h = thread::spawn(move || println!("{}", r));
    h.join().unwrap();
}

نقل قيم متعددة

يمكن لإغلاق واحد يستخدم move التقاط عدة متغيرات في آن واحد. وتُنقل جميعها إلى الخيط.

يفيد ذلك عندما يحتاج العامل إلى بعض المدخلات وإلى تسمية.

use std::thread;

fn main() {
    let label = String::from("sum");
    let nums = vec![1, 2, 3, 4];
    let h = thread::spawn(move || {
        let total: i32 = nums.iter().sum();
        println!("{} = {}", label, total);
    });
    h.join().unwrap();
}

النسخ قبل النقل

إذا احتاج كل من الخيط والدالة الرئيسية إلى قيمة، فانسخوها أولًا. امنحوا إحدى النسختين للخيط واحتفظوا بالأخرى.

ينسخ الاستنساخ البيانات، ولذلك يمتلك كل طرف قيمة مستقلة.

use std::thread;

fn main() {
    let original = String::from("hello");
    let for_thread = original.clone();
    let h = thread::spawn(move || println!("thread: {}", for_thread));
    println!("main: {}", original);
    h.join().unwrap();
}

إعادة البيانات المنقولة

يمكن لخيط يمتلك بيانات منقولة أن يعيدها، معيدًا الملكية إلى الخيط الأب عبر join.

ينقل هذا النمط البيانات إلى الداخل، ثم يعالجها، ثم ينقل النتيجة إلى الخارج.

use std::thread;

fn main() {
    let mut v = vec![3, 1, 2];
    let h = thread::spawn(move || {
        v.sort();
        v
    });
    let sorted = h.join().unwrap();
    println!("{:?}", sorted);
}

سبب ضرورة move

من دون move، يفترض مدقّق الاستعارة أن الإغلاق يستعير البيانات. وبما أن الخيوط المُنشأة لا ترتبط بمدة حياة ثابتة للمتصل، فإن الاستعارات غير آمنة.

تحوّل الكلمة المفتاحية move الاستعارات إلى عمليات نقل ملكية، مما يفي بمتطلب 'static في spawn.

تحقق سريع

اختبروا مدى فهمكم لنقل البيانات إلى الخيوط.

مراجعة

تعلّمتم أن إغلاقات الخيوط يجب أن تمتلك البيانات التي تستخدمها، ويتحقق ذلك باستخدام الكلمة المفتاحية move.

ينقل move الملكية، بينما تُنسخ الأنواع Copy. وتحدد سمة Send ما يمكن نقله بأمان، في حين أن Rc ليس Send.

سترون بعد ذلك كيفية مشاركة البيانات بين الخيوط باستخدام Arc وMutex.

الأسئلة الشائعة

هل درس «نقل البيانات إلى الخيوط» مجاني؟

نعم — نص درس «نقل البيانات إلى الخيوط» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Learn Rust Coding، انتقل إلى CoddyKit PRO. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

ماذا ستتعلم في «نقل البيانات إلى الخيوط»؟

استخدم دوال الإغلاق move بطريقة صحيحة تتمرن على Learn Rust Coding مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Learn Rust Coding؟

لا تُشترط خبرة سابقة. Learn Rust Coding على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «نقل البيانات إلى الخيوط»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Learn Rust Coding هذا؟

نعم. كل درس في Learn Rust Coding يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. إنشاء الخيوط
  2. نقل البيانات إلى الخيوط
  3. المشاركة باستخدام Arc وMutex
  4. ضمّ الخيوط وتجميع النتائج
← العودة إلى Learn Rust Coding